注意
使用 pthread 系列函数编译时,必须手动链接线程库:
bash
gcc test.c -o test -pthread
不加 -pthread 会出现编译 / 运行异常。
为什么必须加 -pthread
-pthread 不只是单纯链接 -lpthread,它同时做两件事:
链接阶段:链接 libpthread 线程库,提供 pthread_create / pthread_self / pthread_exit 等函数的实现;
编译预处理阶段:定义宏、调整线程安全的标准库行为(如 errno 线程局部存储、锁实现)。
线程操作
(1) pthread_create 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_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 文件
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);
return NULL;
}
int main()
{
// 创建新线程
pthread_t thread;
int num = 10;
int tid = pthread_create(&thread, NULL, handle, (void *)&num);
if(tid != 0)
{
char * str = strerror(tid);
printf("%s\n", str);
}
for(int i=0; i < 3; i++)
{
printf("pid: %d, i: %d\n", getpid(), i);
}
sleep(1);
return 0;
}

注意上面的代码部分只有 handle 函数部分是子线程独立运行的代码段。
(2) pthread_self 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
pthread_t pthread_self(void);
2. 参数:无参数。
3. 返回值
该函数调用永远成功,返回调用者线程的 ID(类型为 pthread_t)。
4. 作用
获取当前调用进程的 ID 。
(3) pthread_exit 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
void pthread_exit(void *retval);
2. 参数
retval:线程退出返回值指针- 作用:存放线程结束后要传递给其他线程的返回数据;
- 接收方式:其他线程调用
pthread_join(&tid, &res),res会接收该指针。 - 取值规则:
- 若不需要返回数据,传 NULL;
- 若要传递数据,不能传入栈局部变量地址(线程销毁后栈会释放,内存失效),建议传全局变量 / 堆内存地址;
3. 返回值
函数无返回值,调用后当前线程立刻终止,代码不会回到调用 pthread_exit 的下一行继续执行。
4. 作用
终止一个线程,在哪个线程中调用,就表示终止哪个线程。
2. 样例
创建一个 pthread_exit.c 文件
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());
return NULL;
}
int main()
{
// 创建新线程
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
// 主线程
for(int i = 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");
return 0;
}

可以看到 "main thread die..." 并没有输出,因此可以得到终结线程后的代码将不会执行。
(4) pthread_join 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
int pthread_join(pthread_t thread, 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 文件
c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
int num = 10; //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收
void * rollback(void * arg)
{
printf("child thread id : %ld\n", pthread_self());
sleep(3); // 测试pthread_join是否阻塞
pthread_exit((void *)&num); // 等同于(void *)&num
}
int main()
{
// 创建新线程
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
// 主线程
for(int i = 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");
return 0;
}

(5) pthread_detach 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
int pthread_detach(pthread_t thread);
2. 参数
pthread_t thread- 目标线程 ID,指定要设置为分离态的线程;
- 限制:该线程当前必须是可汇合 (joinable) 状态,不能已经被分离。
3. 返回值
- 成功:返回 0;
- 失败:返回对应错误编号。
4. 作用
- 修改线程属性为分离态,改变资源回收规则
默认新建线程是 joinable,退出后资源残留,必须pthread_join回收;
detach 之后线程一旦结束,操作系统自动释放线程栈、线程管理结构体,不会内存泄漏。 - 解除 "必须等待" 的限制
主线程无需阻塞等待该线程执行完毕,二者完全异步运行。
2. 样例
创建一个 pthread_detach.c 文件
c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
int num = 10; //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收
void * rollback(void * arg)
{
printf("child thread id : %ld\n", pthread_self());
sleep(3); // 测试pthread_detach是否阻塞
return (void *)#
}
int main()
{
// 创建新线程
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
if(ret != 0)
{
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
// 主线程
for(int i = 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");
return 0;
}

可以看到将线程分离后再次进行连接将会错误。
(6) pthread_cancel 函数
1. 函数介绍
1. 函数原型
c
#include <pthread.h>
int pthread_cancel(pthread_t thread);
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 文件
c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
int num = 10; //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收
void * rollback(void * arg)
{
printf("child thread id : %ld\n", pthread_self());
sleep(1);
for(int i = 1; i < 100; i++)
{
printf("child pthread i: %d\n", i);
}
return (void *)#
}
int main()
{
// 创建新线程
pthread_t tid;
int arg = 10;
int ret = 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(int i = 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");
return 0;
}

可以看到子线程并没有输出循环体的内容就被父进程给终止了。这是因为 sleep 函数本身是取消点函数,当取消请求队列中有取消信号则会终止该线程。
线程属性
线程属性类型 pthread_attr_t。
(1) 初始化、释放线程属性资源
c
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
描述 :
pthread_attr_init() 函数会使用默认值初始化 attr 指向的线程属性对象。调用该函数后,可以通过配套系列函数单独修改属性对象里的各项配置,之后该属性对象可以重复用于一次或多次 pthread_create() 创建线程。
对一个已经初始化完成 的属性对象再次调用 pthread_attr_init(),程序行为属于未定义。
当线程属性对象不再使用时,应当调用 pthread_attr_destroy() 销毁释放内部资源。销毁属性对象不会对以往依靠该属性创建出的线程产生任何影响。
属性对象被销毁后,可以再次调用 pthread_attr_init() 重新初始化复用;对已经销毁、未重新初始化的属性对象做任何其他操作,结果都是未定义行为。
返回值 :
两个函数调用成功均返回 0;执行失败时返回非 0 错误码。
(2) 设置、获取线程属性
c
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
描述 :
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 文件
c
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
int num = 10; //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收
void * rollback(void * arg)
{
printf("child thread id : %ld\n", pthread_self());
sleep(1);
return (void *)#
}
int main()
{
// 设置线程属性
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化
int ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置由该属性创建的线程为分离态
if(ret != 0)
{
char * errstr = strerror(ret);
printf("error1 : %s\n", errstr);
}
// 查看新建的属性
int detachstate;
pthread_attr_getdetachstate(&attr, &detachstate);
if(detachstate == PTHREAD_CREATE_DETACHED)
{
printf("新建属性: detached state\n");
}
else if(detachstate == PTHREAD_CREATE_JOINABLE)
{
printf("新建属性: joinable state\n");
}
// 创建新线程
pthread_t tid;
int arg = 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");
return 0;
}
