注意
使用pthread系列函数编译时,必须手动链接线程库:
gcc test.c-otest-pthread不加-pthread会出现编译 / 运行异常。
为什么必须加-pthread-pthread不只是单纯链接-lpthread,它同时做两件事:
链接阶段:链接 libpthread 线程库,提供 pthread_create / pthread_self / pthread_exit 等函数的实现;
编译预处理阶段:定义宏、调整线程安全的标准库行为(如 errno 线程局部存储、锁实现)。
线程操作
(1)pthread_create函数
1. 函数介绍
1. 函数原型
#include<pthread.h>intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);2. 返回值
- 成功:返回 0;
- 失败:返回对应错误编号 (不是之前说的 errno ,获取错误号信息
strerror),*thread 数据无效。
3. 参数
pthread_t *thread
输出参数:创建成功后,新线程 ID 会存入该指针指向内存;创建失败时,*thread 内容未定义。const pthread_attr_t *attr
线程属性结构体指针:- 非 NULL:使用结构体中自定义属性创建线程,结构体需先用
pthread_attr_init()初始化; - NULL:采用系统默认线程属性。
- 非 NULL:使用结构体中自定义属性创建线程,结构体需先用
void *(*start_routine)(void *)
线程入口函数,线程启动后执行该函数,返回值为 void*。void *arg
传递给入口函数start_routine的唯一参数。
4. 作用
在调用进程中创建并启动一条新线程。
2. 样例 (创建一个子线程)
准备一个 pthread_create.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>void*handle(void*arg){printf("子线程已创建...\n");printf("参数arg: %d\n",*(int*)arg);returnNULL;}intmain(){// 创建新线程pthread_tthread;intnum=10;inttid=pthread_create(&thread,NULL,handle,(void*)&num);if(tid!=0){char*str=strerror(tid);printf("%s\n",str);}for(inti=0;i<3;i++){printf("pid: %d, i: %d\n",getpid(),i);}sleep(1);return0;}
注意上面的代码部分只有handle函数部分是子线程独立运行的代码段。
(2)pthread_self函数
1. 函数介绍
1. 函数原型
#include<pthread.h>pthread_tpthread_self(void);2. 参数:无参数。
3. 返回值
该函数调用永远成功,返回调用者线程的 ID(类型为 pthread_t)。
4. 作用
获取当前调用进程的 ID 。
(3)pthread_exit函数
1. 函数介绍
1. 函数原型
#include<pthread.h>voidpthread_exit(void*retval);2. 参数
retval:线程退出返回值指针- 作用:存放线程结束后要传递给其他线程的返回数据;
- 接收方式:其他线程调用
pthread_join(&tid, &res),res会接收该指针。 - 取值规则:
- 若不需要返回数据,传 NULL;
- 若要传递数据,不能传入栈局部变量地址(线程销毁后栈会释放,内存失效),建议传全局变量 / 堆内存地址;
3. 返回值
函数无返回值,调用后当前线程立刻终止,代码不会回到调用 pthread_exit 的下一行继续执行。
4. 作用
终止一个线程,在哪个线程中调用,就表示终止哪个线程。
2. 样例
创建一个 pthread_exit.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>void*rollback(void*arg){printf("child thread id : %ld\n",pthread_self());returnNULL;}intmain(){// 创建新线程pthread_ttid;intarg=10;intret=pthread_create(&tid,NULL,rollback,(void*)&arg);if(ret!=0){char*errstr=strerror(ret);printf("error : %s\n",errstr);}// 主线程for(inti=1;i<3;i++){printf("i = %d\n",i);}printf("tid: %ld, main thread id: %ld\n",tid,pthread_self());// 终止主线程pthread_exit(NULL);printf("main thread die...\n");return0;}
可以看到 “main thread die…” 并没有输出,因此可以得到终结线程后的代码将不会执行。
(4)pthread_join函数
1. 函数介绍
1. 函数原型
#include<pthread.h>intpthread_join(pthread_tthread,void**retval);2. 参数
pthread_t thread- 待等待的目标线程 ID;
- 限制:该线程必须是 joinable(默认新建线程都是 joinable,调用 pthread_detach 后不可 join)。
void **retval- 输出型二级指针,用于接收目标线程的退出返回值;
- 传 NULL:代表不需要接收线程退出结果;
- 非空:会写入
pthread_exit()传入的指针,线程被取消则写入PTHREAD_CANCELED。
3. 返回值
- 成功:返回 0;
- 失败:返回对应错误编号。
4. 作用
该函数会阻塞等待参数thread指定的线程终止。
2. 样例
创建一个 pthread_join.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>intnum=10;//这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收void*rollback(void*arg){printf("child thread id : %ld\n",pthread_self());sleep(3);// 测试pthread_join是否阻塞pthread_exit((void*)&num);// 等同于(void *)&num}intmain(){// 创建新线程pthread_ttid;intarg=10;intret=pthread_create(&tid,NULL,rollback,(void*)&arg);if(ret!=0){char*errstr=strerror(ret);printf("error : %s\n",errstr);}// 主线程for(inti=1;i<3;i++){printf("i = %d\n",i);}printf("tid: %ld, main thread id: %ld\n",tid,pthread_self());// 阻塞等待回收子线程void*thread_val;pthread_join(tid,&thread_val);printf("main thread rece: %d\n",*(int*)thread_val);// 终止主线程pthread_exit(NULL);printf("main thread die...\n");return0;}(5)pthread_detach函数
1. 函数介绍
1. 函数原型
#include<pthread.h>intpthread_detach(pthread_tthread);2. 参数
pthread_t thread- 目标线程 ID,指定要设置为分离态的线程;
- 限制:该线程当前必须是可汇合 (joinable) 状态,不能已经被分离。
3. 返回值
- 成功:返回 0;
- 失败:返回对应错误编号。
4. 作用
- 修改线程属性为分离态,改变资源回收规则
默认新建线程是 joinable,退出后资源残留,必须pthread_join回收;
detach 之后线程一旦结束,操作系统自动释放线程栈、线程管理结构体,不会内存泄漏。 - 解除 “必须等待” 的限制
主线程无需阻塞等待该线程执行完毕,二者完全异步运行。
2. 样例
创建一个 pthread_detach.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>intnum=10;//这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收void*rollback(void*arg){printf("child thread id : %ld\n",pthread_self());sleep(3);// 测试pthread_detach是否阻塞return(void*)#}intmain(){// 创建新线程pthread_ttid;intarg=10;intret=pthread_create(&tid,NULL,rollback,(void*)&arg);if(ret!=0){char*errstr=strerror(ret);printf("error : %s\n",errstr);}// 主线程for(inti=1;i<3;i++){printf("i = %d\n",i);}printf("tid: %ld, main thread id: %ld\n",tid,pthread_self());// 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放ret=pthread_detach(tid);if(ret!=0){char*errstr=strerror(ret);printf("error1 : %s\n",errstr);}// 设置分离后,对分离的子线程进行连接 pthread_join()void*thread_val;ret=pthread_join(tid,&thread_val);if(ret!=0){char*errstr=strerror(ret);printf("error2 : %s\n",errstr);}printf("main thread rece: %d\n",*(int*)thread_val);// 终止主线程pthread_exit(NULL);printf("main thread die...\n");return0;}
可以看到将线程分离后再次进行连接将会错误。
(6)pthread_cancel函数
1. 函数介绍
1. 函数原型
#include<pthread.h>intpthread_cancel(pthread_tthread);2. 参数
pthread_t thread- 目标线程 ID,代表要发送取消信号的线程;
- 支持对自身线程、其他线程发送取消请求。
3. 返回值
- 成功:返回 0;
- 失败:返回对应错误编号。
4. 作用pthread_cancel()函数向thread指定的目标线程发送取消请求。
目标线程是否响应、何时响应这个取消请求,由该线程自身的两个属性控制:state (取消使能状态) 和 type (取消类型)。
5. 两大关键控制属性(目标线程自身控制是否响应取消)
① 取消使能状态pthread_setcancelstate函数设置 state
PTHREAD_CANCEL_ENABLE(默认):接收并处理取消请求;PTHREAD_CANCEL_DISABLE:忽略取消请求,请求排队暂存,直到重新开启。
② 取消类型pthread_setcanceltype函数设置 type
PTHREAD_CANCEL_DEFERRED(默认,延迟取消)
不会立刻终止,仅在线程调用取消点函数(read/write/pthread_join/sleep等系统 IO、同步函数)时才触发取消流程;
业务计算循环、纯 CPU 运算代码段不会被中途打断,安全。PTHREAD_CANCEL_ASYNCHRONOUS(异步取消)
线程任意代码位置都可能被随时终止,风险极高,极易造成资源泄漏、死锁,极少使用。
6. 线程被取消后的固定执行流程
- 逆序执行所有清理函数
pthread_cleanup_push; - 执行线程私有数据 TSD 析构函数;
- 线程正式终止退出。
2. 样例
创建一个pthread_cancel.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>intnum=10;//这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收void*rollback(void*arg){printf("child thread id : %ld\n",pthread_self());sleep(1);for(inti=1;i<100;i++){printf("child pthread i: %d\n",i);}return(void*)#}intmain(){// 创建新线程pthread_ttid;intarg=10;intret=pthread_create(&tid,NULL,rollback,(void*)&arg);if(ret!=0){char*errstr=strerror(ret);printf("error : %s\n",errstr);}// 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放pthread_detach(tid);// 终止指定子进程pthread_cancel(tid);for(inti=1;i<3;i++){printf("i = %d\n",i);}printf("tid: %ld, main thread id: %ld\n",tid,pthread_self());// 终止主线程pthread_exit(NULL);printf("main thread die...\n");return0;}
可以看到子线程并没有输出循环体的内容就被父进程给终止了。这是因为sleep函数本身是取消点函数,当取消请求队列中有取消信号则会终止该线程。
线程属性
线程属性类型pthread_attr_t。
(1) 初始化、释放线程属性资源
#include<pthread.h>intpthread_attr_init(pthread_attr_t*attr);intpthread_attr_destroy(pthread_attr_t*attr);描述:pthread_attr_init()函数会使用默认值初始化attr指向的线程属性对象。调用该函数后,可以通过配套系列函数单独修改属性对象里的各项配置,之后该属性对象可以重复用于一次或多次pthread_create()创建线程。
对一个已经初始化完成的属性对象再次调用pthread_attr_init(),程序行为属于未定义。
当线程属性对象不再使用时,应当调用pthread_attr_destroy()销毁释放内部资源。销毁属性对象不会对以往依靠该属性创建出的线程产生任何影响。
属性对象被销毁后,可以再次调用pthread_attr_init()重新初始化复用;对已经销毁、未重新初始化的属性对象做任何其他操作,结果都是未定义行为。
返回值:
两个函数调用成功均返回 0;执行失败时返回非 0 错误码。
(2) 设置、获取线程属性
#include<pthread.h>intpthread_attr_getdetachstate(constpthread_attr_t*attr,int*detachstate);intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);描述:pthread_attr_setdetachstate()函数用于设置线程属性对象attr的分离状态属性,参数detachstate指定目标状态。该属性决定了使用此属性对象创建出的线程,默认是可汇合(joinable)还是分离(detached) 状态。
detachstate仅支持以下两个宏取值:
PTHREAD_CREATE_DETACHED
使用该属性创建的线程,直接为分离态。PTHREAD_CREATE_JOINABLE
使用该属性创建的线程,为可汇合态。
刚通过pthread_attr_init()初始化完成的线程属性对象,分离状态默认值PTHREAD_CREATE_JOINABLE。
pthread_attr_getdetachstate()函数会将属性对象attr中存储的分离状态,存入detachstate指针指向的内存中返回。
返回值:
两个函数调用成功均返回 0;执行失败时,返回非 0 的错误码。
(3) 样例
创建一个 pthread_attr.c 文件
#include<stdio.h>#include<string.h>#include<pthread.h>#include<sys/types.h>#include<unistd.h>intnum=10;//这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收void*rollback(void*arg){printf("child thread id : %ld\n",pthread_self());sleep(1);return(void*)#}intmain(){// 设置线程属性pthread_attr_tattr;pthread_attr_init(&attr);// 初始化intret=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);// 设置由该属性创建的线程为分离态if(ret!=0){char*errstr=strerror(ret);printf("error1 : %s\n",errstr);}// 查看新建的属性intdetachstate;pthread_attr_getdetachstate(&attr,&detachstate);if(detachstate==PTHREAD_CREATE_DETACHED){printf("新建属性: detached state\n");}elseif(detachstate==PTHREAD_CREATE_JOINABLE){printf("新建属性: joinable state\n");}// 创建新线程pthread_ttid;intarg=10;ret=pthread_create(&tid,&attr,rollback,(void*)&arg);// 创建子线程的同时设置属性if(ret!=0){char*errstr=strerror(ret);printf("error2 : %s\n",errstr);}printf("tid: %ld, main thread id: %ld\n",tid,pthread_self());pthread_attr_destroy(&attr);// 释放线程属性资源// 终止主线程pthread_exit(NULL);printf("main thread die...\n");return0;}