72、多线程基础:pthread_create 的使用
多线程编程,说白了就是让程序同时干几件事。你想想看,单线程就像只有一个工人的流水线,干完一件才能干下一件。多线程呢,就是多招几个工人,各干各的活,效率自然就上去了。
在嵌入式Linux开发中,POSIX线程库(pthread)是事实标准。我这些年做过的项目,从路由器到工业控制器,几乎都离不开它。今天咱们就聊聊最基础的——pthread_create。
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,失败返回错误码
核心要点:pthread_create 成功返回0,不是返回线程ID!我见过不少新手写成 if (pthread_create(...) == -1),结果死活抓不到错误。
一个最简单的例子
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* worker(void* arg) {
int id = *(int*)arg;
printf("线程 %d 开始干活\n", id);
sleep(2);
printf("线程 %d 干完了\n", id);
return NULL;
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
if (pthread_create(&t1, NULL, worker, &id1) != 0) {
perror("创建线程1失败");
return 1;
}
if (pthread_create(&t2, NULL, worker, &id2) != 0) {
perror("创建线程2失败");
return 1;
}
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("主线程:所有工作完成\n");
return 0;
}
编译时别忘了加 -lpthread:
gcc -o demo demo.c -lpthread
传参的坑,我踩过
上面代码里,我把 id1 和 id2 的地址传给了线程。为什么不用值传递?因为 pthread_create 的第四个参数是 void*,只能传指针。
但这里有个大坑——传局部变量的地址要小心。我曾经在项目里写过这样的代码:
// 错误示范!
for (int i = 0; i < 5; i++) {
pthread_create(&threads[i], NULL, worker, &i);
}
结果呢?所有线程拿到的 i 值都一样,因为循环跑得飞快,等线程真正去读 i 的时候,它早就变成5了。这就是经典的「传地址陷阱」。
避坑指南:我曾经在批量创建线程时犯过这个错,排查了整整半天。正确的做法是动态分配内存,或者用数组传不同的地址。
正确写法:
int* ids = malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
ids[i] = i;
pthread_create(&threads[i], NULL, worker, &ids[i]);
}
// 别忘了在线程结束后 free(ids)
线程属性,你了解多少?
第二个参数 attr 传NULL虽然省事,但有些场景你必须设置属性。比如:
- 栈大小:嵌入式系统内存紧张,默认栈可能太大
- 分离状态:设置
PTHREAD_CREATE_DETACHED后,线程结束自动释放资源 - 调度策略:实时系统可能需要设置优先级
设置属性的流程:
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&attr, 64 * 1024); // 64KB栈
pthread_create(&thread, &attr, worker, arg);
pthread_attr_destroy(&attr); // 用完销毁
个人习惯:我一般把属性初始化封装成一个函数,比如 init_thread_attr_default(),这样项目里所有线程的创建风格统一,也方便后期调整。
pthread_create 的返回值处理
很多人只检查是否等于0,其实错误码能告诉你更多信息:
| 错误码 | 含义 | 常见原因 |
|---|---|---|
| EAGAIN | 资源不足 | 线程数超限、内存不够 |
| EINVAL | 参数无效 | attr 设置有问题 |
| EPERM | 权限不足 | 调度策略/优先级设置无权限 |
我建议用 strerror() 打印错误信息,比光看数字直观多了:
int ret = pthread_create(&thread, NULL, worker, arg);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %s\n", strerror(ret));
}
知识体系:pthread_create 的核心逻辑
下面这张图帮你理清思路:
几个实用建议
- 线程数量要可控:别一股脑创建几百个线程。嵌入式系统资源有限,我一般用线程池来管理。
- 主线程别急着退出:主线程退出会干掉所有子线程。用
pthread_join或pthread_detach处理好生命周期。 - 错误处理要到位:pthread_create 失败的原因很多,别只打印个"创建失败"就完事。
- 注意线程安全:多个线程访问同一变量时,记得加锁。这个后面章节会细讲。
总结一下:pthread_create 是多线程编程的起点。参数不多,但每个都有讲究。传参的地址问题、属性设置、错误码处理——这些细节决定了你的多线程程序是稳定运行还是莫名其妙崩溃。
嗯,今天就聊到这儿。多线程这东西,光看理论不够,得动手写、动手调。下次遇到线程创建失败,你知道该查哪里了吧?
公众号:蓝海资料掘金营,微信deep3321