尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

linux系统编程(一):pthread常用函数

linux系统编程(一):pthread常用函数
📅 发布时间:2026/6/24 5:51:17

Linux pthread 常用函数实战 —— 从 create 到 TLS

一行pthread_create起线程很简单,但要安全地停下、等到它退出、回收资源、还能传数据回来—— 一套 pthread API 才够用。这篇把最常用的 14 个函数串起来,每个配最小可运行的 demo。


0. 一个例子

把 1 ~ 10 亿求和,单线程跑约 2 秒。开 4 个线程并行算,每个负责 1/4,理论上能压到 0.5 秒。

主线程 ├─ worker 1:1 ~ 2.5 亿 ├─ worker 2:2.5 亿 ~ 5 亿 ├─ worker 3:5 亿 ~ 7.5 亿 └─ worker 4:7.5 亿 ~ 10 亿 主线程等所有 worker 完成 → 把 4 个结果加起来

这一个场景就用到:

  • pthread_create起线程
  • pthread_join等结果
  • pthread_exit/ return 带返回值
  • pthread_self调试时区分线程

下面一个一个看。


1. 线程生命周期:create / join / detach / exit

1.1 pthread_create —— 起线程

intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
参数含义
thread输出:新线程的 tid 写到这里
attr属性(栈大小、是否 detached 等),传 NULL 用默认
start_routine入口函数,签名固定void *(void *)
arg传给入口函数的参数

最小例子:

#include<pthread.h>#include<stdio.h>void*worker(void*arg){intid=*(int*)arg;printf("worker %d running\n",id);returnNULL;}intmain(void){pthread_ttid;intid=42;pthread_create(&tid,NULL,worker,&id);pthread_join(tid,NULL);return0;}

⚠️arg的生命周期:上面这个例子主线程pthread_join阻塞着,所以id这个栈变量是活的。如果改成pthread_detach不等就 return,id已经被回收,worker 读到的就是垃圾。

1.2 pthread_exit —— 主动退出 + 带返回值

voidpthread_exit(void*retval);

retval会被pthread_join拿到。直接return等价于pthread_exit:

void*worker(void*arg){longsum=0;for(inti=1;i<=1000000;i++)sum+=i;return(void*)sum;// 等价于 pthread_exit((void *)sum)}

1.3 pthread_join —— 阻塞等退出 + 拿返回值 + 回收资源

intpthread_join(pthread_tthread,void**retval);

阻塞当前线程,等thread退出,把它的返回值写到retval。

⚠️不 join 也不 detach= 线程退出后资源永远不回收(“僵尸线程”),是常见的资源泄漏原因。

把开头那个并行求和例子完整写出来:

#include<pthread.h>#include<stdio.h>#defineN4#defineTOTAL1000000000Ltypedefstruct{longstart,end;}range_t;void*sum_range(void*arg){range_t*r=arg;longs=0;for(longi=r->start;i<=r->end;i++)s+=i;return(void*)s;}intmain(void){pthread_ttids[N];range_tranges[N];longstep=TOTAL/N;longtotal=0;for(inti=0;i<N;i++){ranges[i].start=i*step+1;ranges[i].end=(i+1)*step;pthread_create(&tids[i],NULL,sum_range,&ranges[i]);}for(inti=0;i<N;i++){void*ret;pthread_join(tids[i],&ret);// 等退出 + 拿返回值total+=(long)ret;}printf("sum = %ld\n",total);return0;}

1.4 pthread_detach —— “我不打算等了”

intpthread_detach(pthread_tthread);

线程被分离后:

  • 不需要 join
  • 退出时资源自动回收
  • 不能再 join 了(再 join 会返回错误)

适用场景:fire-and-forget 的后台任务,比如日志写入、心跳上报、网络服务的 per-connection handler。

void*background_log(void*arg){while(1){write_log_to_disk();sleep(1);}returnNULL;}intmain(void){pthread_ttid;pthread_create(&tid,NULL,background_log,NULL);pthread_detach(tid);// 不打算等了do_main_work();return0;}

也可以在创建时直接设PTHREAD_CREATE_DETACHED属性:

pthread_attr_tattr;pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);pthread_create(&tid,&attr,...);pthread_attr_destroy(&attr);

2. 取消机制:cancel / cleanup / state / type

2.1 pthread_cancel —— 请求其他线程退出

intpthread_cancel(pthread_tthread);

向目标线程发取消请求。注意:只是请求,不是强制退出。目标线程在到达"取消点"(cancellation point)时才真的退出。

常见的取消点:read/write/poll/sleep/pthread_cond_wait/pthread_join等阻塞 syscall。完整列表见man 7 pthreads。

例子:可中断的下载

void*download_worker(void*arg){while(more_data){intn=recv(sock,buf,sizeof(buf),0);// 取消点write(file,buf,n);// 取消点}returnNULL;}intmain(void){pthread_ttid;pthread_create(&tid,NULL,download_worker,NULL);sleep(5);pthread_cancel(tid);// 5 秒后取消下载pthread_join(tid,NULL);return0;}

2.2 pthread_cleanup_push / pop —— cancel 安全的资源释放

线程在阻塞点被 cancel 时,已经持有的资源(mutex、内存、文件、socket 等)需要自动释放。用 cleanup handler:

voidcleanup_unlock(void*arg){pthread_mutex_unlock((pthread_mutex_t*)arg);}void*worker(void*arg){pthread_mutex_lock(&m);pthread_cleanup_push(cleanup_unlock,&m);// 注册 handlerpthread_cond_wait(&cv,&m);// 取消点;如果被 cancel,handler 会被自动调pthread_cleanup_pop(1);// 1 = 执行 handler;0 = 仅注销returnNULL;}

⚠️cleanup_push 和 cleanup_pop 必须配对在同一个作用域,因为它们底层是宏,依赖局部变量做记账。

2.3 pthread_setcancelstate / setcanceltype —— 控制何时响应

intpthread_setcancelstate(intstate,int*oldstate);// PTHREAD_CANCEL_ENABLE - 默认// PTHREAD_CANCEL_DISABLE - 屏蔽 cancel(请求被挂起,state 改回 ENABLE 后才生效)intpthread_setcanceltype(inttype,int*oldtype);// PTHREAD_CANCEL_DEFERRED - 默认,到 cancellation point 才取消// PTHREAD_CANCEL_ASYNCHRONOUS - 立即取消(很危险,几乎不用)

进入临界区前可以暂时禁用 cancel:

intoldstate;pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstate);do_critical_thing();pthread_setcancelstate(oldstate,NULL);

2.4 pthread_testcancel —— 显式检查

如果一段代码完全没有 cancellation point(比如纯 CPU 循环),又想响应 cancel,手动插入:

for(inti=0;i<1000000;i++){do_calc(i);if(i%1000==0)pthread_testcancel();// 显式检查}

3. 线程标识:self / setname_np

3.1 pthread_self —— 拿自己的 tid

pthread_tpthread_self(void);

调试 log 常用:

printf("[tid=%lu] processing\n",(unsignedlong)pthread_self());

⚠️pthread_t在 glibc 上是unsigned long,但 POSIX 标准没规定具体类型。跨平台代码要用pthread_equal(t1, t2)比较,不要直接用==。

3.2 pthread_setname_np —— 设线程名(调试用)

intpthread_setname_np(pthread_tthread,constchar*name);

线程名最长 16 字节(含\0)。设了之后top -H、ps -eL、htop、gdb info threads都能看到,调试很方便:

void*worker(void*arg){pthread_setname_np(pthread_self(),"downloader");...}

_np后缀是 “non-portable”,但 Linux / BSD / macOS 都支持(macOS 上pthread_setname_np只接一个参数)。


4. 一次性初始化:pthread_once

intpthread_once(pthread_once_t*once_control,void(*init_routine)(void));

保证init_routine在多线程环境下只被执行一次,且其他线程会等第一次执行完。

经典场景:线程安全的单例 / 懒初始化。

#include<pthread.h>staticpthread_once_tonce=PTHREAD_ONCE_INIT;staticconfig_t*g_config=NULL;staticvoidinit_config(void){g_config=load_config_from_file();}config_t*get_config(void){pthread_once(&once,init_config);returng_config;}

不管多少个线程同时调get_config,init_config只会执行一次,其他线程被阻塞到第一次执行完。

比"双重检查锁定"(DCLP)写法更简洁,且不会出错。


5. 线程局部存储(TLS):key_create / setspecific / getspecific

intpthread_key_create(pthread_key_t*key,void(*destructor)(void*));intpthread_setspecific(pthread_key_tkey,constvoid*value);void*pthread_getspecific(pthread_key_tkey);

每个线程拥有自己独立的"key 对应的值"。经典例子:errno就是 TLS 实现的(每个线程的 errno 互不影响)。

staticpthread_once_tonce=PTHREAD_ONCE_INIT;staticpthread_key_tmy_key;staticvoiddestructor(void*value){free(value);}staticvoidinit_key(void){pthread_key_create(&my_key,destructor);}char*get_thread_buffer(void){pthread_once(&once,init_key);char*buf=pthread_getspecific(my_key);if(!buf){buf=malloc(1024);pthread_setspecific(my_key,buf);}returnbuf;}

线程退出时destructor自动被调用,释放各自的 buf。

C11 还提供_Thread_local关键字(gcc 也支持__thread),更简洁:

__threadcharbuf[1024];// 每个线程独立一份

只是__thread不能动态分配 + 自动释放,复杂场景还是用pthread_key_*。


6. 线程生命周期一图流

线程从pthread_create进入 Running 状态后,有三条退出路径:

  1. 正常退出:return或pthread_exit→ Terminated
  2. 被取消:pthread_cancel触发 + 到达 cancellation point → 执行 cleanup handlers → Terminated
  3. 进程退出:所有线程一起死

Terminated 之后两条回收路径:

  • 被 join:joined 的线程有人收尸,资源回收
  • detached:不需要 join,资源自动回收

7. cancel 的完整流程


pthread_cancel(tid)` 调用后:

  1. 内核给目标线程标记一个 “pending cancel”
  2. 目标线程在到达 cancellation point 时检查标记
  3. 如果 cancelstate 是 ENABLE 且 type 是 DEFERRED → 触发取消
  4. 按 LIFO 倒序执行所有pthread_cleanup_push注册的 handler
  5. 调用所有 TLS destructor
  6. 线程退出(相当于 pthread_exit(PTHREAD_CANCELED))

这条链路上任何一步崩了(比如 cleanup handler 自己抛异常),结果是 UB。所以 cleanup handler 必须简单、不阻塞、不抛错。


8. 总结表

函数用途必须配对/注意
pthread_create创建线程之后必须 join 或 detach
pthread_join等退出 + 拿返回值 + 回收跟 create 配对
pthread_detach不等,自动回收跟 create 配对(二选一)
pthread_exit主动退出 + 带返回值-
pthread_self拿自己 tid比较用pthread_equal
pthread_setname_np设线程名(调试)名字 ≤ 16 字节
pthread_cancel请求取消配 cleanup handler
pthread_cleanup_push/pop资源清理 handler必须同作用域
pthread_setcancelstate启停 cancelenable/disable
pthread_setcanceltypedeferred/asyncasync 危险,别用
pthread_testcancel显式检查 cancel纯 CPU 循环里用
pthread_once一次性初始化配 PTHREAD_ONCE_INIT
pthread_key_create创建 TLS key注册 destructor
pthread_setspecific/getspecific写/读 TLS配 once 初始化 key

9. 最容易踩的 6 个坑

  1. arg 生命周期:传栈变量给 detached 线程,主线程出栈后 worker 读到垃圾。要么malloc,要么保证主线程比子线程活久。

  2. 不 join 也不 detach:线程退出后资源永远不回收,俗称"僵尸线程"。

  3. double join:同一个 tid join 两次是 UB。join 完 tid 就失效了。

  4. cancel 后没 cleanup:mutex / 内存 / 文件描述符泄漏。有 cancel 一定要有 cleanup_push。

  5. cleanup_push/pop 不在同一作用域:编译报错(底层是宏 + 局部变量)。

  6. pthread_t类型假设:不要假设它是 int 或 unsigned long,跨平台用pthread_equal比较。


10. 收尾

线程的核心 API 不多,难的是配对纪律:

  • create配join或detach
  • lock配unlock(会在第二篇细讲)
  • cancel配cleanup_push
  • key_create配destructor

任何一对配偏了,都是潜在的资源泄漏或死锁。

把这 14 个函数掌握,普通工程的多线程需求 90% 能搞定。剩下 10%(高性能调度、特殊信号处理、跨进程同步)才需要进一步学 attr 细节、信号、共享内存、futex 等。

相关新闻

  • 建筑石材选型的数据分析:用pandas对比8类石材性能
  • 别再找 Prompt 模板了:提示词的本质,是你和 AI 的任务契约
  • 【万字精讲】计算机网络高频填空简答18题:从死记硬背到体系化精通(原题+深度解析+避坑指南)

最新新闻

  • SVG图片钓鱼攻击:从XML到恶意代码的隐蔽攻击链剖析
  • SRC漏洞挖掘实战:从信息搜集到逻辑漏洞的完整狩猎指南
  • 函数级时间分析集成:数据管道模式与动态策略实践
  • DeepSeek-V4终端编程助手:深思考+上下文感知的AI协作者
  • OpenClaw:Windows原生零代码AI工作流引擎
  • PXN20微控制器时钟系统深度解析:从架构原理到低功耗实战

日新闻

  • 终极指南:如何用shadPS4在电脑上免费畅玩PS4游戏
  • 打造个性化Instagram Clone:主题定制与用户体验优化技巧
  • 未来展望:RoseTTAFold-All-Atom的发展路线图与社区支持资源汇总

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号