前两天学习完了进程有关的内容,今天学习线程这部分的内容,线程和进程很容易搞混,我们还是要搞懂定义,但是学习完了进程,线程这部分内容相对容易理解。
一、什么是线程
线程被叫做轻量级进程,它同样拥有创建、调度、消亡完整流程。
进程和线程的区别:
进程:操作系统资源分配的最小单位
每个进程拥有0‑4G完整虚拟内存空间,分为文本段、数据段、堆等区域,进程之间内存互相独立,互不干扰。
线程:操作系统任务调度的最小单位
线程跑在进程的地址空间里面。同一个进程里的多个线程,共享堆、全局变量、静态变量、字符串常量、代码段;但是每个线程有自己独立的栈空间,局部变量是线程私有的。
简单总结:
进程拥有整套资源;线程只拥有自己栈,其余全部借用父进程的资源。
二、进程与线程核心函数对比
进程 线程
fork() 创建 pthread_create() 创建
exit() 退出 pthread_exit() 退出
wait() 回收子进程 pthread_join() 回收线程
-
调度
线程调度和进程调度规则一致,宏观上看多个线程并发运行,微观上CPU分片交替执行。
-
线程消亡
线程结束之后,如果不调用pthread_join回收资源,就会产生僵尸线程,占用系统资源。
三、线程函数
编译线程程序必须加 -pthread 参数,gcc test.c -o test -pthread。
-
pthread_create 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:
创建线程。
参数:
thread:传出参数,存放新线程ID(TID)
attr:线程属性,一般传NULL,使用默认属性
start_routine:线程要执行的函数,函数指针
arg:传给线程函数的参数
返回值:
成功返回0,失败返回错误码
调用完这个函数,新线程立刻开始调度,主线程和子线程谁先跑是不确定的。
-
pthread_exit 结束线程
void pthread_exit(void *retval);
功能:
用来在线程函数内部结束当前线程。
参数:
retval是线程退出的状态值,可以被pthread_join拿到。
注意:不要在线程里面直接用exit(),exit会把整个进程直接结束,所有线程全部消失。
-
pthread_join 回收线程
int pthread_join(pthread_t thread, void **retval);
功能:
等待指定线程结束,回收线程栈资源,具备阻塞特性。
参数:
thread:要等待回收的线程ID
retval:接收线程退出返回值,不需要接收就填NULL
返回值:
成功0,失败返回错误码
阻塞含义:如果子线程还没跑完,主线程卡在join这里原地等待,直到子线程结束才往下走。
四、分离属性 detach(不用join回收)
普通线程必须pthread_join回收,否则僵尸线程。
设置分离属性之后:线程结束,操作系统自动回收资源,程序就不需要调用pthread_join。
相关接口:
- pthread_attr_init:初始化线程属性结构体
- pthread_attr_setdetachstate:设置分离状态
- pthread_attr_destroy:销毁属性结构体,释放空间
两种写法:创建的时候设置分离属性;或者线程内部调用pthread_detach(self)。
五、多进程 、多线程的优缺点
- 运行效率:多线程 > 多进程
线程共享进程地址空间,创建切换开销小;进程要复制完整内存,开销更大。
- 安全性:多进程 > 多线程
进程内存完全独立,一个进程崩溃,不会影响别的进程。
同一个进程内,任意一个线程崩溃,整个进程直接挂掉,全部线程一起结束。
- 通信实现:多线程 > 多进程
多线程天然共享全局变量、堆内存,直接读写就可以通信。
但是多个线程同时改共享变量,会出现资源竞争,需要加锁保护。
多进程地址空间互相隔离,没有共享内存,必须管道、共享内存这类特殊机制才能通信,实现复杂。
- 编程复杂度:多进程 < 多线程
多线程要处理资源竞争、锁、僵尸线程;多进程通信麻烦,但不会出现线程互相干扰的问题。