一、线程的概念
在引入线程之前,进程作为资源分配的最小单位(分配得到了CPU的时间、内存等),操作系统通过调度算法实现多进程并发执行,共用CPU,但由于创建或撤销进程时,系统都要为之分配或回收资源,限制了并发程度的提高。后来,为了减少进程间切换的开销,可把进程作为资源分配单位和调度单位两个属性分隔开,于是有了线程,即作为程序执行的最小单位,同一进程中的线程共享相同地址空间,甚至线程之间也共享大部分资源。采用多线程来实现多任务开发可以极大减小切换开销。
Linux中不区分进程、线程,而是通过pthread线程库来实现线程,也叫轻量级进程LWP。由于线程是进程的一部分,多个线程共享进程的很多资源,包括:可执行的指令、静态数据、进程中打开的fd、当前工作目录、用户ID、用户组ID等,当然线程也有自己的私有资源,包括线程ID(TID)、堆栈、errno、优先级、执行状态和属性等
二、线程的创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg)
@param: thread 子线程指针 创建完自动给thread赋值TID
attr 线程属性指针 给NULL默认为结合属性,线程的资源回收得配合join
routine 线程函数名 参数、返回值都为void * 类型的函数
arg 线程函数的参数指针 (多参数时设置结构体变量,将变量地址给指针即可)
@return: 成功返回0失败返回错误码
注意事项:
- gcc编译时要加上 -lpthread 链接线程库
- 进程退出,线程也全部结束退出。线程创建需要时间,如果进程退出得快,线程很可能得不到执行
- 线程里可用pthread_self(void)获得自身TID 打印时格式化字符为%lu
- 线程的参数传递,实参须(void*)强转,形参给void *arg,解引用(取值)须先强转回原类型
- 1.指针传递: 如 *(int *)arg
- 2.值传递:如 int(arg) 但要保证数据长度正确 值传递时 实参也可以直接给值 即 值作为地址 传给形参
- 查看线程命令 ps -eLf
三、线程的退出
仅仅结束退出线程,不涉及资源的释放和回收
void pthread_exit(void *retval) //return 也可以退出线程
@param: retval 一般用字符串 可被其他线程通过pthread_join获取
四、线程的回收
主动回收 结合属性的线程资源,阻塞等待指定线程退出
int pthread_join(pthread_t thread, void **retval)
@param:retval 指针的地址!
成功后retval指向exit的退出状态并返回0,失败返回错误码
示例:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
printf("This is child thread\n");
sleep(25);
pthread_exit("thread return");
}
int main(){
pthread_t tid[100];
void *retv;
int i;
for(i=0;i<100;i++){
pthread_create(&tid[i],NULL,func,NULL);
}
for(i=0;i<100;i++){
pthread_join(tid[i],&retv);
printf("thread ret=%s\n",(char*)retv);
}
while(1){
sleep(1);
}
}
五、线程的分离
因为exit只能结束线程但不能释放资源,对于属性为结合的线程,还需要join来主动阻塞等待回收,十分不方便,可以令其和主线程分离,线程退出后系统主动回收资源,所以不用也不能调用join函数再重复回收
有以下两种方法:
1.调用线程分离函数
int pthread_detach(pthread_t thread)
功能:指定线程分离 通常在子线程里调用,与主线程分离
2.创建线程时设置其属性为分离
pthread_attr_t attr; //定义一个线程属性结构体变量
pthread_attr_init(&attr); //初始化线程属性
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性为分离
pthread_t tid;
pthread_create(&tid,&attr,线程函数名,线程函数参数指针);
线程的内存可通过 top -p PID 查看进程中的线程详细信息
六、线程的取消
杀死线程!
前提是线程中有取消点(有阻塞的系统调用如sleep、printf),如果没有,可以pthread_testcancel()添加取消点
int pthread_cancel(pthread_t thread)
int pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
//取消失能,后续想要恢复使能,DISABLE换成ENABLE
线程被杀死的时间取决于其他线程何时调用该函数,而能否真正杀死还要看线程里是否取消使能了,如果失能了直到重新使能才可以成功杀死,也就是说,cancel线程后,相当于给线程发送一个取消信号,当线程执行完取消点同时取消使能则被杀死,后续代码不再执行;如果子线程取消失能了,执行完取消点也不停下,取消信号未决,等到取消使能后马上被杀死
杀死线程,线程的资源也得不到回收
子线程也可以杀死自己
示例:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
printf("This is child thread\n");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); //失能
// while(1)
{
sleep(5);
pthread_testcancel();
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); //使能
printf("should not print\n");
while(1){
sleep(1);
}
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retv);
// printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
七、线程的清理
线程被取消通常都在exit之前,容易出现malloc的资源泄露未得到释放,此时可以在线程中调用清理函数,然后在清理函数中释放。
void pthread_cleanup_push(清理函数名,参数指针)
void pthread_cleanup_pop(int execute) //参数尽量>0
两个清理函数必须成对出现,否则编译不成功,可以写多对,遵循栈先入后出的原则
清理函数的执行条件:线程被杀死、线程调用exit(return都不行)、pop参数>0
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void cleanup(void *arg){
printf("cleanup,arg=%s\n",(char*)arg);
}
void cleanup2(void* arg){
printf("cleanup2,arg=%s\n",(char*)arg);
}
void *func(void *arg){
printf("This is child thread\n");
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);//线程可在任意时间点取消
pthread_cleanup_push(cleanup,"abcd");
pthread_cleanup_push(cleanup2,"efgh");
//while(1)
{
sleep(1);
}
return "1234";
while(1){
printf("sleep\n");
sleep(1);
}
pthread_exit("thread return");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
sleep(10);
pthread_exit("thread return");
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid,NULL,func,NULL);
sleep(1);
// pthread_cancel(tid);
pthread_join(tid,&retv);
printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}