一、POSIX线程库:从零开始
多线程编程,说白了就是让程序同时干好几件事。我刚开始接触这玩意儿时,总觉得它很神秘——怎么就能让CPU同时跑多个任务呢?后来才明白,其实就是在进程内部创建多个执行流,每个流独立执行自己的代码。
POSIX线程库(pthread)是Linux/Unix系统下最基础的线程API。我个人习惯把它当作多线程编程的"普通话"——不管你在哪个平台,这套接口基本都通用。今天我们就从最核心的四个函数入手:创建线程、等待线程、退出线程、获取线程ID。
核心要点:使用pthread库时,编译要加 -lpthread 链接选项。我见过不少新手卡在这一步,代码写得没问题,编译却报错——其实就是忘了链接线程库。
二、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,失败返回错误码。
我的经验:线程函数的参数传递,我建议用结构体指针。直接传整数虽然方便,但容易遇到类型转换问题。我曾经在项目里传了一个int的地址,结果线程还没开始读,主线程就把变量改了——典型的竞态条件。
来看个最简单的例子:
#include <stdio.h>
#include <pthread.h>
void* print_msg(void* arg) {
char* msg = (char*)arg;
printf("线程说: %s\n", msg);
return NULL;
}
int main() {
pthread_t tid;
char* hello = "Hello from thread!";
int ret = pthread_create(&tid, NULL, print_msg, (void*)hello);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %d\n", ret);
return 1;
}
// 等待线程结束
pthread_join(tid, NULL);
return 0;
}
三、pthread_join:等待线程
创建线程后,主线程如果不等待,很可能子线程还没跑完,主线程就退出了——整个进程都结束了,子线程自然也被强行终止。
函数原型:
int pthread_join(pthread_t thread, void **retval);
- thread:要等待的线程ID
- retval:二级指针,用来接收线程的返回值。如果不需要,传NULL
注意:pthread_join是阻塞的。也就是说,调用它的线程会一直等,直到目标线程退出。如果你不想阻塞,可以考虑pthread_tryjoin_np(非标准扩展)或者用条件变量。
获取线程返回值:
void* compute(void* arg) {
int* num = (int*)arg;
int* result = malloc(sizeof(int));
*result = (*num) * 2;
return (void*)result;
}
int main() {
pthread_t tid;
int input = 21;
pthread_create(&tid, NULL, compute, &input);
void* retval;
pthread_join(tid, &retval);
printf("计算结果: %d\n", *(int*)retval);
free(retval); // 记得释放内存!
return 0;
}
嗯,这里要注意:线程返回的指针指向的内存,必须保证在线程退出后仍然有效。我习惯用malloc分配,然后在主线程里free——这样生命周期最清晰。
四、pthread_exit:线程主动退出
线程退出有三种方式:
- 线程函数执行完,自然返回
- 调用pthread_exit主动退出
- 被其他线程调用pthread_cancel取消
pthread_exit的原型:
void pthread_exit(void *retval);
它和return的区别?说白了,return是从线程函数返回,而pthread_exit可以在函数任意位置退出。比如:
void* worker(void* arg) {
printf("开始工作...\n");
// 检查到错误条件,提前退出
if (some_error_condition) {
pthread_exit((void*)-1);
}
printf("工作完成\n");
pthread_exit((void*)0);
}
避坑指南:我曾经在项目里犯过一个错——在线程函数里调用了exit()。结果整个进程直接退出了,其他线程全被强行终止。记住:exit()是进程级的,pthread_exit()才是线程级的。
五、线程ID的获取与比较
每个线程都有唯一的ID。获取方式有两种:
| 函数 | 说明 | 使用场景 |
|---|---|---|
| pthread_self() | 返回当前线程的ID | 线程内部获取自己的ID |
| pthread_equal() | 比较两个线程ID是否相等 | 判断当前线程是否是某个特定线程 |
代码示例:
#include <pthread.h>
#include <stdio.h>
void* check_id(void* arg) {
pthread_t my_id = pthread_self();
pthread_t* expected = (pthread_t*)arg;
if (pthread_equal(my_id, *expected)) {
printf("没错,我就是那个被创建的线程\n");
} else {
printf("咦?ID对不上\n");
}
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, check_id, &tid);
pthread_join(tid, NULL);
return 0;
}
重要提醒:不要用 == 直接比较pthread_t!pthread_t可能是结构体,不是整数。一定要用pthread_equal()。我在代码审查时见过好几次这种错误——用==比较,在Linux上碰巧能跑,换到其他Unix系统就崩了。
六、知识体系总览
下面这张图总结了本章的核心内容:
七、实战要点总结
最后,我把这章的核心要点再捋一遍:
- 创建线程:pthread_create,注意参数传递方式,建议用结构体
- 等待线程:pthread_join,阻塞等待,记得处理返回值内存
- 退出线程:pthread_exit,线程级退出,别和exit搞混
- 线程ID:pthread_self获取,pthread_equal比较,别用==
我的建议:刚开始学多线程,别急着搞复杂的同步机制。先把这四个函数用熟,写几个小demo跑一跑。我当年就是先写了个"多线程打印数字"的程序,看着不同线程交替输出,才真正理解了"并发"的含义。
好了,这一章就到这里。记住:多线程编程,基础打牢了,后面学互斥锁、条件变量、信号量才会顺手。代码多写几遍,自然就熟了。
公众号:蓝海资料掘金营,微信deep3321