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

传参的坑,我踩过

上面代码里,我把 id1id2 的地址传给了线程。为什么不用值传递?因为 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_create 调用 传入 4 个参数 参数有效性检查 attr、函数指针等 内核资源分配 栈、TCB、调度实体 启动线程执行 返回 0 表示成功 返回错误码 EAGAIN / EINVAL / EPERM 关键参数 thread: 线程ID输出 attr: 属性(NULL=默认) start_routine: 入口函数 arg: 传给函数的参数 常见陷阱 传局部变量地址 忘记 -lpthread 链接

几个实用建议

  1. 线程数量要可控:别一股脑创建几百个线程。嵌入式系统资源有限,我一般用线程池来管理。
  2. 主线程别急着退出:主线程退出会干掉所有子线程。用 pthread_joinpthread_detach 处理好生命周期。
  3. 错误处理要到位:pthread_create 失败的原因很多,别只打印个"创建失败"就完事。
  4. 注意线程安全:多个线程访问同一变量时,记得加锁。这个后面章节会细讲。

总结一下:pthread_create 是多线程编程的起点。参数不多,但每个都有讲究。传参的地址问题、属性设置、错误码处理——这些细节决定了你的多线程程序是稳定运行还是莫名其妙崩溃。

嗯,今天就聊到这儿。多线程这东西,光看理论不够,得动手写、动手调。下次遇到线程创建失败,你知道该查哪里了吧?


公众号:蓝海资料掘金营,微信deep3321