一、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:线程主动退出

线程退出有三种方式:

  1. 线程函数执行完,自然返回
  2. 调用pthread_exit主动退出
  3. 被其他线程调用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系统就崩了。

六、知识体系总览

下面这张图总结了本章的核心内容:

POSIX线程库入门核心结构 pthread_create 创建线程 pthread_join 等待线程结束 pthread_exit 主动退出 线程ID 获取与比较 参数:thread, attr, func, arg 返回值:0成功,非0错误码 阻塞等待线程退出 获取线程返回值 任意位置退出线程 vs exit():进程级退出 pthread_self() 获取 pthread_equal() 比较 避坑总结 1. 编译加 -lpthread 2. 不要用 == 比较 pthread_t 3. 线程内用 pthread_exit,别用 exit()

七、实战要点总结

最后,我把这章的核心要点再捋一遍:

  • 创建线程:pthread_create,注意参数传递方式,建议用结构体
  • 等待线程:pthread_join,阻塞等待,记得处理返回值内存
  • 退出线程:pthread_exit,线程级退出,别和exit搞混
  • 线程ID:pthread_self获取,pthread_equal比较,别用==

我的建议:刚开始学多线程,别急着搞复杂的同步机制。先把这四个函数用熟,写几个小demo跑一跑。我当年就是先写了个"多线程打印数字"的程序,看着不同线程交替输出,才真正理解了"并发"的含义。

好了,这一章就到这里。记住:多线程编程,基础打牢了,后面学互斥锁、条件变量、信号量才会顺手。代码多写几遍,自然就熟了。


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