2. POSIX线程库(pthreads)基础
好,咱们正式开始玩多线程了。在Linux世界里,多线程编程几乎绕不开一个东西——pthreads。我当年刚接触它的时候,觉得这名字挺唬人,什么POSIX线程库,听起来像什么高深标准。其实说白了,它就是一套C语言的线程操作接口,你把它当成一把瑞士军刀就行。
2.1 pthreads简介
pthreads,全称POSIX Threads,是IEEE制定的一套线程标准接口。为什么它这么重要?因为几乎所有类Unix系统都支持它——Linux、macOS、FreeBSD,甚至某些嵌入式系统。你写一份pthreads代码,基本可以跨平台编译运行。
我个人习惯把pthreads看作「线程操作的API集合」。它提供了什么?创建线程、销毁线程、线程同步(互斥锁、条件变量)、线程私有数据等等。嗯,这里要注意:pthreads不是语言的一部分,它是库。所以你得告诉编译器去链接它。
核心概念:pthreads中的线程是轻量级进程。它们共享同一进程的地址空间,但各自拥有独立的栈和寄存器上下文。这意味着——线程间通信非常高效,但也容易踩坑(后面会讲)。
2.2 编译链接方法(-lpthread)
写pthreads代码,第一步不是写代码,而是搞清楚怎么编译。我见过不少新手,代码写得没问题,编译死活过不去,最后发现是忘了加链接选项。
编译命令长这样:
gcc -o myprogram myprogram.c -lpthread
关键就在这个 -lpthread。它告诉链接器去链接libpthread.so这个动态库。如果你用CMake,可以这样写:
target_link_libraries(myprogram pthread)
注意:有些老系统上,你可能需要写成 -pthread(带一个横杠)。这不仅是链接选项,还会影响编译器的预处理宏。我建议你优先用 -lpthread,如果不行再试 -pthread。
我曾经在一个嵌入式项目里,交叉编译时忘了加这个选项,结果链接阶段报了一堆「undefined reference to pthread_create」。排查了半天,最后发现就是少了个链接参数。从那以后,我写Makefile第一件事就是把-lpthread写上。
2.3 创建第一个线程(pthread_create)
好,咱们来写第一个多线程程序。先看函数原型:
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);
参数解释一下:
- thread:输出参数,创建成功后存放线程ID
- attr:线程属性,传NULL表示使用默认属性
- start_routine:线程入口函数,格式必须是
void* func(void*) - arg:传给入口函数的参数
返回值:成功返回0,失败返回错误码。
来看一个完整的例子:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
int id = *(int*)arg;
printf("线程 %d 正在运行...\n", id);
return NULL;
}
int main() {
pthread_t tid;
int thread_id = 1;
int ret = pthread_create(&tid, NULL, thread_func, &thread_id);
if (ret != 0) {
fprintf(stderr, "创建线程失败,错误码: %d\n", ret);
return 1;
}
printf("主线程继续执行...\n");
sleep(1); // 简单等待子线程完成
return 0;
}
你想想看,这段代码里发生了什么?主线程调用pthread_create,系统会创建一个新线程,从thread_func函数开始执行。主线程自己则继续往下走。两个线程是并发运行的,谁先执行完不一定。
小技巧:线程入口函数的参数是void*类型,你可以传任何东西——整数、结构体指针、甚至NULL。但要注意,传局部变量地址时要小心生命周期。我习惯动态分配内存传进去,在线程里用完再释放。
2.4 等待线程结束(pthread_join)
上面那个例子有个问题——主线程用sleep等待子线程,这太粗糙了。如果子线程执行时间超过1秒呢?或者子线程早就结束了,主线程还在傻等?
正确的做法是用pthread_join。它会让调用线程阻塞,直到目标线程退出。函数原型:
int pthread_join(pthread_t thread, void **retval);
- thread:要等待的线程ID
- retval:输出参数,接收线程的返回值(如果不需要,传NULL)
改一下上面的例子:
#include <stdio.h>
#include <pthread.h>
void* thread_func(void* arg) {
int id = *(int*)arg;
printf("线程 %d 正在运行...\n", id);
return (void*)42; // 返回一个值
}
int main() {
pthread_t tid;
int thread_id = 1;
void* retval;
pthread_create(&tid, NULL, thread_func, &thread_id);
pthread_join(tid, &retval); // 等待线程结束
printf("线程已退出,返回值: %ld\n", (long)retval);
return 0;
}
这里有个细节:pthread_join会回收线程资源。如果你不调用它,线程结束后会变成僵尸线程,占用系统资源。我见过一个服务程序,创建了大量线程却不join,最后把系统线程数耗尽了。
避坑指南:我曾经在一个网络代理项目里,主线程创建了工作线程后直接退出了。结果工作线程还在跑,但进程已经结束了——所有线程都被强制终止。正确的做法是:主线程必须等待所有子线程结束,或者使用pthread_detach让线程自动清理。
知识体系总览
下面这张图,把本章的核心逻辑串起来了:
这张图把本章的四个知识点串成了流水线:先搞定编译链接,然后创建线程,线程执行任务,最后主线程等待并回收。每一步都缺一不可。
本章小结:pthreads是Linux多线程编程的基石。你只需要记住三个函数——pthread_create、pthread_join,以及编译时加-lpthread。剩下的,都是在这些基础上搭积木。下一章我们会深入线程的生命周期管理,包括分离线程、取消线程等高级话题。
公众号:蓝海资料掘金营,微信deep3321