简 述: 前面几篇,学习了 Linux 下多进程使用 fork() 分析的其构造和原理;这里进一步,探究一下如何创建多线程,以及多线程和多进程之间的差异。最后写几个实例;验证分析。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
编程环境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
线程:
线程和进程的理论概念不再称述;这里主要是从虚拟地址空间和 PCB 的角度来看,主、子线程。
Linux 中,系统是不认识线程还是进程的,它们只认识 PCB;
Unix 和 Linux 的线程在系统的底层实现是不一样的,有区别的,但是它们在应用层是看起来没什么区别;Linux 中一开始没有线程,是 Windows 中先有的,后面被前辈们移植过来的,所以 Linux 中线程的实现,系统底层是由创建进程改了改。
主线程和子线程:
- 共享:
- 用户区内,除了栈区是不共享的,其余都是不共享的。
- 不共享:
- 栈区(当有 1 主 + 4 子线程时候,栈区会被平分为 5 份)
- 共享:
在 Linux 下:
- 线程就是进程 – 轻量级的进程
- 对于内核来说,线程就是进程(内核只会用)
多线程和多进程的区别:
- 多进程共享的资源:
- 代码
- 文件描述符
- 内存映射区 –mmap
- 多线程共享的资源:
- 堆
- 全局变量
- 相比多进程,更加节省系统资源;对于系统 CPU 轮转时间片来说,不论是线程还是进程,它不认识,只认 PCB。
- 多进程共享的资源:
安装线程 man page:
安装线程 man page 命令:
sudo apt install manpages-posix-dev
查看指定线程的 LWP 号:
- 线程号和线程 ID 是有区别的
- 线程号是给内核看的
- 查看方式:
- 找到程序的进程 ID
ps -Lf pid
一个例子,查看火狐浏览器程序由多线程构成:
如: Linux 下 查看火狐浏览器,发现其是由多线程构成的
ps ajx | grep "firefox"
ps -Lf 31102
pthread_create():
- 作用: 创建子线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
主线程退出,子线程一定会被强制结束退出。
参数:
- thread: 传出参数,线程创建成功后,会设置一个合适值,通常比较大。
- attr: 线程属性,默认使用 NULL
- start_routine: 子线程的处理函数(也被称为回调函数)
- arg: 子线程的处理函数的参数
返回值:
函数调用成功返回 0,失败返回错误号;但此处的错误号不是之前那个能够用 perror() 打印的错误号,这两个不是一个东西,他们各自使用的库不通用。
想要打印错误信息,需要使用标准库函数 strerror()
int ret = pthread_creator(...) if (ret != 0) printf("error: %s\n", strerror(ret))
写一个例子:
例子一:
使用不带参数的,子线程的处理函数,验证子线程执行的代码,只有该回调函数里面的内容。
代码示例:
#include <stdio.h> #include <unistd.h> #include <pthread.h> void* myfun(void* arg); int main(int argc, char *argv[]) { pthread_t pthread = 0; //创建一个子线程 int ret = pthread_create(&pthread, NULL, myfun, NULL); printf("parent thread id: %ld\n", pthread_self); sleep(2); //避免主线程运行后,就死亡了,而子线程没机会 for (int i = 0; i < 5; i++) { //验证子线程,并不会执行这里面的代码,只会执行回调函数 muyfun 里面的 printf("i = %d\n", i); } return 0; } void* myfun(void* arg) { printf("child thread id: %ld\n", pthread_self); return NULL; }
运行结果:
例子二:
写一个能够创建多个子线程的函数,要使用到其参数。
注意其是地址传递的话,有这样一种可能,该变量是在主线程的地址空间中,其他几个子线程都是在该线程中读取改值;但是由于他们分别抢占 CPU 时间片,会导致结果可能和预期结果不符;此时可以考虑使用值传递。
代码示例:
#include <stdio.h> #include <unistd.h> #include <pthread.h> int num = 13; //设置为全局变量,在全局区域,共享 void* myfun(void* arg); int main(int argc, char *argv[]) { void* p = (void *)# //传一个地址进去(voi* 也是 4 个字节) pthread_t id[5] = {0}; for (int i = 0; i < 5; i++) { pthread_create(&(id[i]), NULL, myfun, p); printf("i = %d, thread id: %ld\n", i, id[i]); } return 0; } void* myfun(void* arg) { printf("num = %d, child thread id: %ld\n", (*((int *)arg))++, pthread_self()); return NULL; }
程序分析:
可以看出,其其创建多个子线程成功了🤓🤓,和预期结果相符。
运行结果:
其它
如果将全局变量
int num = 13;
放到 main() 里面,当做局部变量的话,就会出现如下结果!!! 有一处小的瑕疵,有点困惑?注意其 mun 的值,有时候会异常,怀疑是多个线程抢占 CPU 的时间片 + 是传递的地址,或者有其他系统或者数据修改过该值???所以有时候值得范围是非 13-17;异常变得巨大; 原因还在找,或者你知道的话,可以在下方留言告诉我,学习知识这种事,达者为先,相互指点一下后到“晚辈”。
下载地址:
欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。