文章目录
线程概念
LWP:light weight process 轻量级的进程,本质仍是进程(在 Linux 环境下);查看 LWP 号:ps –Lf pid 查看指定线程的 LWP 号
进程:独立地址空间,拥有 PCB
线程:有独立的 PCB,但没有独立的地址空间(共享)
区别:在于是否共享地址空间。 独居(进程);合租(线程)。
Linux 下: 线程:最小的执行单位;进程:最小分配资源单位,可看成是只有一个线程的进程;轻量级进程(light-weight process),也有 PCB,创建线程使用的底层函数和进程一样,都是 clone;
gcc编译注意事项:应用-lpthread 参数链接线程库;
线程共享和非共享资源
线程共享资源
- 文件描述符表
- 内存地址空间 (.text/.data/.bss/heap/共享库)
- 每种信号的处理方式
- 用户 ID 和组 ID
- 当前工作目录
线程非共享资源
- 线程 id
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno 变量
- 信号屏蔽字
- 调度优先级
线程优、缺点
优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、不支持gdb 3. 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。
线程控制函数
pthread_self()
作用:获取线程 ID。其作用对应进程中 getpid() 函数。
函数原型:pthread_t pthread_self(void);
参数:无参数;
返回值:成功:0; 失败:无
-
线程 ID:pthread_t 类型,本质:在 Linux 下为无符号整数(%lu),其他系统中可能是结构体实现
-
线程 ID 是进程内部,识别标志。(两个进程间,线程 ID 允许相同)
-
注意:不应使用全局变量 pthread_t tid,在子线程中通过 pthread_create 传出参数来获取线程 ID,而应使用pthread_self。
pthread_create()
作用:创建一个新线程;其作用对应进程中 fork() 函数。
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数 1:传出参数,保存系统为我们分配好的线程 ID
参数 2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数 3:函数指针做函数参数,回调函数,指向线程主函数(线程体),该函数运行结束,则线程结束。
参数 4:线程主函数执行期间所使用的参数。
返回值:成功:0; 失败:错误号
函数使用说明:在一个线程中调用 pthread_create()创建新的线程后,当前线程从 pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给 pthread_create 函数参数3中的函数指针 start_routine 决定,这是一个回调函数;start_routine 函数接收一个参数,是通过pthread_create 函数参数4中的 arg 参数传递给它的,该参数的类型为 void *,这个指针按什么类型解释由调用者自己定义。start_routine 的返回值类型也是 void *,这个指针的含义同样由调用者自己定义。所以回调函数在传参和return返回的时候要进行强制类型转换;start_routine 返回时,这个线程就退出了,其它线程可以调用pthread_join 得到 start_routine 的返回值,类似于父进程调用 waitpid()得到子进程的退出状态,稍后详细介绍 pthread_join。
pthread_exit()
作用:将单个线程退出;起作用对应进程中的exit()函数。
函数原型:void pthread_exit(void *retval);
参数:retval 表示线程退出状态,通常传 NULL
返回值:无返回值
结论:线程中,禁止使用 exit 函数,会导致进程内所有线程全部退出。
所以,多线程环境中,尽量不使用 exit 函数,取而代之使用 pthread_exit 函数,将单个线程退出。 另注意,pthread_exit 或者 return 返回的指针所指向的内存单元必须是全局的或者是用 malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
总结:
- return:返回到函数调用者那里;
- exit(): 将整个进程退出;
- pthread_exit():将调用该函数的线程退出;
pthread_join()
作用:阻塞等待线程退出,获取线程退出状态 ;其作用对应进程中 waitpid() 函数。
函数原型:int pthread_join(pthread_t thread, void **retval)
参数1:待回收的线程ID
参数2:存储线程的结束退出状态
返回值:成功:0; 失败:错误号
如何理解参数2中的void **retval:
进程中:main 返回值、exit()=>int;等待子进程结束 wait 函数参数–>int *;
线程中:线程主函数返回值、pthread_exit()=>void *;等待线程结束 pthread_join 函数参数–>void **;
pthread_detach()
作用:实现线程分离
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
函数原型:int pthread_detach(pthread_t thread)
参数:待设置分离状态的线程ID
返回值:成功:0;失败:错误号
说明:
- 一般情况下,线程终止后,其终止状态一直保留到其它线程调用 pthread_join 获取它的状态为止。但是线程也可以被置为 detach 状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
- 不能对一个已经处于 detach 状态的线程调用 pthread_join,这样的调用将返回 EINVAL 错误。也就是说,如果已经对一个线程调用了 pthread_detach 就不能再调用 pthread_join 了。
pthread_cancel()
作用:杀死(取消)线程;其作用,对应进程中 kill() 函数。
函数原型:int pthread_cancel(pthread_t thread)
参数:待杀死的线程ID
返回值:成功:0;失败:错误号
说明:
- 线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。类似于玩游戏存档,必须到达指定的场所(存档点)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点。
- 取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用 creat,open,pause,close,read,write… 执行命令 man 7 pthreads 可以查看具备这些取消点的系统调用列表。
- 可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用 pthread_testcancel函数自行设置一个取消点。
- 被取消的线程, 退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。因此当我们对一个已经被取消的线程使用 pthread_join回收时,得到的返回值为-1。
总结:终止某个线程而不终止整个进程,有三种方法:
- 从线程主函数 return。这种方法对主控线程不适用,从 main 函数 return 相当于调用 exit;退出当前整个进程。
- 一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。
- 线程可以调用 pthread_exit 终止自己。
线程属性设置分离线程
线程属性初始化
初始化线程属性
int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号
销毁线程属性所占用的资源
int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号
注意:应先初始化线程属性,再 pthread_create 创建线程
线程的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当 pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
线程分离状态的函数:
设置线程属性,分离 or 非分离
函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
获取程属性,分离 or 非分离
函数原型:int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
参数1: attr:已初始化的线程属性
参数2:detachstate:两个宏定义
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
函数调用错误检查
#include <string.h>
if(ret != 0)//ret函数调用返回值判断
{
fprintf(stderr,“xxx error”,strerror(ret));
exit(1);
}