Linux 系统编程(6)——线程基础

一、为什么需要线程?

进程的创建是为了提高并发 ,但进程是重量级的------创建和切换的开销很大。线程的出现,正是为了在保持并发能力的同时,降低系统开销。

1.1 进程与线程的本质区别

对比项 进程(Process) 线程(Thread)
定位 资源分配的最小单位 CPU 调度的基本单位
侧重 资源分配单位 执行体、执行单位
别称 重量级进程 轻量级进程(LWP)
组成 PCB + text | data | bss | 堆 | 栈 线程ID + 程序计数器(PC) + 一组相关寄存器 + 栈空间
地址空间 独立的虚拟地址空间 共享所属进程的地址空间
创建开销 大(需要复制页表、文件描述符表等) 小(只需分配栈和少量管理结构)

关键关系:

  1. 线程共享进程的资源(代码段、数据段、堆、文件描述符等)
  2. 线程存在于进程中,一个进程至少有一个线程
  3. 进程分为单线程进程多线程进程

1.2 Linux 内核中的线程

Linux 早期并不原生支持线程,而是通过 clone() 系统调用来实现------它创建调用进程的一个拷贝,并与调用进程共享相同的地址空间。

重要事实: 在 Linux 内核中,实际上并不严格区分进程和线程

IBM 和 Red Hat 联合开发了 NPTL(New/Native POSIX Thread Library) ------这是 Linux 上的线程函数库,提供了标准的 POSIX 线程操作接口。我们后续使用的所有 pthread_* 系列函数都来自这个库。


二、线程的四大生命周期

线程操作与进程类似,分为四个阶段:

复制代码
创建 → 执行 → 退出 → 资源回收

2.1 线程创建 ------ pthread_create

复制代码
int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void *),
                   void *arg);

参数说明:

参数 含义
thread 输出参数,用于接收新线程的 ID
attr 线程属性,传 NULL 表示使用默认属性(可结合、非分离)
start_routine 线程的执行函数------线程创建后从这个函数开始运行
arg 传给线程执行函数的参数

返回值: 成功返回 0,失败返回错误码(注意不是 -1)。

示例:最简单的线程创建

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

// 线程执行函数
// 参数 arg 来自 pthread_create 的第四个参数
// 本例中为 NULL
void *do_thread(void *arg)
{
    // pthread_self() 获取当前线程自身的 ID
    printf("---do_thread ---- tid = %ld\n", pthread_self());
    return NULL;  // 线程函数返回,线程结束
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    // 创建线程:属性为 NULL(默认),执行函数为 do_thread,参数为 NULL
    int ret = pthread_create(&tid, NULL, do_thread, NULL);
    if (ret != 0)  // 非零表示创建失败
    {
        errno = ret;        // 将错误码设置到 errno
        perror("pthread_create fail");
        return -1;
    }

    printf("pthread_create success\n");
    printf("tid = %ld\n", tid);                  // 打印子线程 ID
    printf("main tid = %ld\n", pthread_self());  // 打印主线程 ID
    sleep(1);  // 主线程等待 1 秒,确保子线程有时间执行
    return 0;
}

注意: 线程之间是对等关系,不存在进程间的父子关系。


2.2 线程执行 ------ 传参与共享资源

线程共享进程空间,操作同一个资源时会相互影响。下面通过两个线程操作同一变量来展示:

示例:两个线程操作共享变量

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

// 线程1:每次将 var 加 1
void *do_thread1(void *arg)
{
    int *var = arg;  // 接收主线程传来的变量地址
    while (1)
    {
        (*var)++;  // 对共享变量做 +1 操作
        printf("---do_thread1 ---- %d \n", *var);
        sleep(1);  // 每秒操作一次
    }
    return NULL;
}

// 线程2:每次将 var 加 2
void *do_thread2(void *arg)
{
    int *var = arg;  // 同样接收变量地址
    while (1)
    {
        (*var) += 2;  // 对共享变量做 +2 操作
        printf("---do_thread2 ---- %d \n", *var);
        sleep(1);
    }
    return NULL;
}

int main(int argc, const char *argv[])
{
    int var = 0;        // 共享变量
    pthread_t tid[2];

    // 创建线程1,将 &var 作为参数传入
    int ret = pthread_create(&tid[0], NULL, do_thread1, &var);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    // 创建线程2,同样传入 &var
    ret = pthread_create(&tid[1], NULL, do_thread2, &var);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    printf("pthread_create success\n");
    while (1)
        sleep(1);  // 主线程无限等待,保持进程存活
    return 0;
}

运行结果(示例):

复制代码
---do_thread1 ---- 1
---do_thread2 ---- 3
---do_thread1 ---- 4
---do_thread2 ---- 6
---do_thread1 ---- 7
...

可以看到两个线程交替修改同一个变量 var,这正是资源共享、相互影响 的体现。在没有同步机制的情况下,这属于竞态条件(Race Condition),实际项目中需要用互斥锁等机制来保护。


2.3 线程退出

线程有四种退出方式

方式 说明
1. 调用 pthread_exit() 主动退出,可携带退出状态值
2. 从线程执行函数中 return 等价于调用 pthread_exit(return值)
3. 被其他线程 pthread_cancel() 被动取消
4. 进程中任意线程调用 exit() 或 main 函数 return 导致整个进程终止,所有线程都结束
pthread_exit 函数
复制代码
void pthread_exit(void *retval);

功能: 结束调用线程。参数 retval 是线程带出的退出状态值(一个指针)。

注意事项:

  • pthread_exit 传递的是退出状态值对应空间的地址
  • 如果想获取该线程的退出状态值,需要由另一个线程通过 pthread_join 来获取
  • 如果在 main 线程中调用 pthread_exit(),只是结束了主线程的执行,并不会结束整个进程。进程会等待所有子线程执行结束后才销毁

示例:线程返回字符串

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

void *do_thread(void *arg)
{
    static int ret = 10;
    while (1)
    {
        printf("---do_thread ---- tid = %ld\n", pthread_self());
    }
    // 以下三种退出方式效果等价(在实际代码中只会用一种):
    // return NULL;
    // pthread_exit(&ret);
    return "i am dead....";  // 返回一个字符串指针作为退出状态值
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, do_thread, NULL);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    printf("pthread_create success\n");
    printf("tid = %ld\n", tid);
    printf("main tid = %ld\n", pthread_self());

#if 0  // 以下代码被条件编译禁用,但展示了如何获取线程返回值
    void *retval = NULL;
    pthread_join(tid, &retval);           // 等待子线程结束,获取退出值
    // printf("retval = %d\n", *(int *)retval);  // 如果返回的是 int 指针
    printf("retval = %s\n", (char *)retval);     // 返回的是字符串指针
#endif

    pthread_exit(NULL);  // 主线程退出(但进程不会立即结束)
    return 0;
}

2.4 线程资源回收 ------ pthread_join

复制代码
int pthread_join(pthread_t thread, void **retval);
参数 含义
thread 要等待的线程的 tid
retval 输出参数,获取线程的退出状态值

返回值: 成功返回 0,失败返回错误码。

前提: 线程必须是可结合属性(Joinable)------这是创建线程时的默认属性。

示例:无人机多线程模型(控制、视频、传输、存储)

这个示例模拟了一个无人机系统,包含 4 个子线程分别负责控制、视频、传输和存储功能:

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

// 定义线程函数指针类型,方便批量管理
typedef void *(*threadFunc_t)(void *);

// 控制线程:运行 5 秒
void *do_ctrl(void *arg)
{
    static int ret = 1;  // static 变量确保函数返回后地址仍有效
    int n = 5;
    while (n)
    {
        printf("-----%s----\n", __func__);  // 打印函数名 "do_ctrl"
        sleep(1);
        --n;
    }
    return &ret;  // 返回退出状态值的地址
}

// 视频线程:运行 4 秒
void *do_video(void *arg)
{
    static int ret = 2;
    int n = 4;
    while (n)
    {
        printf("-----%s----\n", __func__);
        sleep(1);
        --n;
    }
    return &ret;
}

// 传输线程:运行 3 秒
void *do_trans(void *arg)
{
    static int ret = 3;
    int n = 3;
    while (n)
    {
        printf("-----%s----\n", __func__);
        sleep(1);
        --n;
    }
    return &ret;
}

// 存储线程:运行 2 秒
void *do_store(void *arg)
{
    static int ret = 4;
    int n = 2;
    while (n)
    {
        printf("-----%s----\n", __func__);
        sleep(1);
        --n;
    }
    return &ret;
}

int main(int argc, const char *argv[])
{
    pthread_t tid[4];
    // 用函数指针数组统一管理 4 个线程函数
    threadFunc_t tfunc[4] = {do_ctrl, do_video, do_trans, do_store};

    int i = 0;
    // 批量创建 4 个线程
    for (i = 0; i < 4; ++i)
    {
        int ret = pthread_create(&tid[i], NULL, tfunc[i], NULL);
        if (ret != 0)
        {
            errno = ret;
            perror("pthread_create fail");
            return -1;
        }
    }

    // 批量回收 4 个线程,并获取各自的退出状态值
    for (i = 0; i < 4; ++i)
    {
        int *retval = NULL;
        // 阻塞等待第 i 个线程结束,并将退出值地址存入 retval
        pthread_join(tid[i], (void **)&retval);
        // 通过指针解引用获取实际的返回值
        printf("ret = %d \n", *retval);
    }
    return 0;
}

运行结果(示例):

复制代码
-----do_ctrl----
-----do_video----
-----do_trans----
-----do_store----
-----do_ctrl----
-----do_video----
-----do_trans----
-----do_store----
-----do_ctrl----
-----do_video----
-----do_ctrl----
-----do_ctrl----
ret = 1
ret = 2
ret = 3
ret = 4

要点: 返回值指针必须指向 static 变量或全局变量/堆内存,不能指向线程栈上的局部变量------否则线程退出后栈被销毁,指针变成悬垂指针。


三、线程属性与分离设置

3.1 两种线程属性

属性 说明
可结合(Joinable,默认) 线程结束后需要通过 pthread_join 回收资源
分离(Detached) 线程结束后系统自动回收 资源,无需也不能 pthread_join

3.2 设置分离属性的两种方式

方式一:通过属性对象(创建时设置)
cs 复制代码
pthread_attr_t attr;
pthread_attr_init(&attr);                                    // 初始化属性对象
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置分离属性
pthread_create(&tid, &attr, do_thread, NULL);                // 用该属性创建线程
pthread_attr_destroy(&attr);                                 // 销毁属性对象
方式二:创建后动态设置(使用 pthread_detach
cs 复制代码
pthread_create(&tid, NULL, do_thread, NULL); // 默认可结合属性
pthread_detach(tid);                          // 创建后标记为分离状态

示例:使用 pthread_detach 设置分离属性

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

void *do_thread(void *arg)
{
    printf("---do_thread ---- tid = %ld\n", pthread_self());
    return NULL;
}

int main(int argc, const char *argv[])
{
    // 以下是被注释的"方式一"代码:
    // pthread_attr_t attr;
    // pthread_attr_init(&attr);
    // pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    // int ret = pthread_create(&tid, &attr, do_thread, NULL);

    pthread_t tid;
    // 使用默认属性(可结合)创建线程
    int ret = pthread_create(&tid, NULL, do_thread, NULL);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    pthread_detach(tid);  // 创建后将线程标记为分离状态
    // pthread_attr_destroy(&attr);  // 方式一需要销毁属性对象

    printf("pthread_create success\n");
    printf("tid = %ld\n", tid);
    printf("main tid = %ld\n", pthread_self());
    sleep(1);

    // 对已分离的线程调用 pthread_join 会失败
    if ((ret = pthread_join(tid, NULL)) != 0)
    {
        errno = ret;
        perror("pthread_join fail");  // 会打印此错误:Invalid argument
        return -1;
    }
    return 0;
}

运行输出:

复制代码
---do_thread ---- tid = xxxxx
pthread_create success
tid = xxxxx
main tid = xxxxx
pthread_join fail: Invalid argument

关键点: 已经被 pthread_detach 的线程,再调用 pthread_join 会失败返回 EINVAL

3.3 子线程回收策略

场景 策略
子线程能在有限时间内结束 使用 pthread_join 正常等待回收
子线程可能休眠或阻塞 等待一定时间后强制回收
子线程必须长时间运行 设置为分离属性,不再回收

四、线程取消 ------ pthread_cancel

复制代码
int pthread_cancel(pthread_t th);

功能: 向目标线程发送取消请求。线程之间可以相互取消

示例:子线程取消主线程

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

pthread_t mtid = 0;  // 全局变量,保存主线程 ID

void *do_thread(void *arg)
{
    printf("---do_thread ---- tid = %ld\n", pthread_self());
    sleep(1);
    pthread_cancel(mtid);  // 子线程取消主线程
    return NULL;
}

void cleanup(void *arg)
{
    printf("cleanup ----\n");  // 线程清理函数
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    mtid = pthread_self();  // 记录主线程自身的 ID

    int ret = pthread_create(&tid, NULL, do_thread, NULL);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    // 注册清理函数:当主线程被取消时会触发
    pthread_cleanup_push(cleanup, NULL);
    printf("pthread_create success\n");
    printf("tid = %ld\n", tid);
    printf("main tid = %ld\n", pthread_self());

    sleep(5);  // 主线程睡眠 5 秒(但 1 秒后就会被子线程取消)
    // pthread_exit(NULL);

    while (1)
        sleep(1);

    pthread_cleanup_pop(0);  // 正常流程中弹出(不执行)
    return 0;
}

运行结果:

复制代码
---do_thread ---- tid = xxxxx
pthread_create success
tid = xxxxx
main tid = xxxxx
cleanup ----       ← 主线程被取消时,触发了清理函数

注意: pthread_cleanup_pop(0) 中参数为 0 表示弹出但不执行清理函数。但因为主线程在 sleep(5) 期间被子线程取消了,清理函数会由取消机制自动触发执行。


五、线程清理函数 ------ pthread_cleanup_push / pop

这是线程版的"退出清理"机制,类似于进程中的 atexit()

5.1 函数原型

c

复制代码
// 注册清理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);

// 弹出清理函数
void pthread_cleanup_pop(int execute);
参数 含义
routine 清理函数的入口地址
arg 传给清理函数的参数
execute 非零值:弹出并执行 清理函数;0:弹出但不执行

5.2 触发方式

清理函数在以下三种情况下会被自动触发:

触发条件 说明
pthread_cleanup_pop(非零值) 主动弹出并执行
pthread_exit() 被调用 线程退出时自动触发
pthread_cancel() 被调用 线程被取消时自动触发

5.3 实际代码中的使用模式

复制代码
pthread_cleanup_push(cleanup, NULL);   // 注册清理函数
    // ... 线程的工作代码 ...
    // 如果这里发生 pthread_cancel 或 pthread_exit,
    // cleanup 会被自动调用
pthread_cleanup_pop(0);                 // 正常结束,弹出但不执行

示例:pthread_cleanup_push/poppthread_cancel 配合

cs 复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

// 线程执行函数
void *do_thread(void *arg)
{
    printf("---do_thread ---- tid = %ld\n", pthread_self());
    return NULL;
}

// 线程清理函数------当线程被取消或调用 pthread_exit 时执行
void cleanup(void *arg)
{
    printf("cleanup ----\n");
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, do_thread, NULL);
    if (ret != 0)
    {
        errno = ret;
        perror("pthread_create fail");
        return -1;
    }

    // 在主线程中注册清理函数
    pthread_cleanup_push(cleanup, NULL);

    printf("pthread_create success\n");
    printf("tid = %ld\n", tid);
    printf("main tid = %ld\n", pthread_self());
    sleep(1);        // 等待子线程执行

    // pthread_cleanup_pop(1);  ← 如果参数为 1,会直接执行 cleanup

    return 0;        // 主线程正常返回
}

展开原理: pthread_cleanup_pushpthread_cleanup_pop 在底层实际上是 ,它们内部使用 do { ... } while(0) 结构配合 setjmp/longjmp 来实现清理函数的注册和调用。因此它们必须成对使用,不能缺少任何一个。


六、综合实战:线程计算器

需求: 从主线程获取用户输入的表达式(如 3.5*2.0),组织成结构体传给子线程,子线程计算后将结果存回结构体,主线程输出结果。

复制代码
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

// 定义计算结构体
typedef struct
{
    float a;    // 第一个操作数
    float b;    // 第二个操作数
    char c;     // 运算符 (+, -, *, /)
    float d;    // 计算结果
} cal_t;

// 加法线程
void *do_add(void *arg)
{
    cal_t *p = arg;         // 接收结构体指针
    p->d = p->a + p->b;    // 计算结果直接存回结构体
    return NULL;
}

// 减法线程
void *do_sub(void *arg)
{
    cal_t *p = arg;
    p->d = p->a - p->b;
    return NULL;
}

// 乘法线程
void *do_mul(void *arg)
{
    cal_t *p = arg;
    p->d = p->a * p->b;
    return NULL;
}

// 除法线程
void *do_div(void *arg)
{
    cal_t *p = arg;
    p->d = p->a / p->b;   // 注意:未处理除零错误
    return NULL;
}

int main(int argc, const char *argv[])
{
    cal_t s;
    printf("Input a pression:");
    // 格式化输入:如 "3.5*2.0" 会被解析为 a=3.5, c='*', b=2.0
    scanf("%f%c%f", &s.a, &s.c, &s.b);

    pthread_t tid;
    int ret = 0;

    // 根据运算符创建对应的计算线程
    switch (s.c)
    {
    case '+':
        ret = pthread_create(&tid, NULL, do_add, &s);  // 传入结构体地址
        if (ret != 0)
        {
            errno = ret;
            perror("pthread_create fail");
            return -1;
        }
        pthread_join(tid, NULL);       // 等待计算完成
        printf("result = %.2f\n", s.d); // 从结构体读取结果
        break;
    case '-':
        ret = pthread_create(&tid, NULL, do_sub, &s);
        if (ret != 0)
        {
            errno = ret;
            perror("pthread_create fail");
            return -1;
        }
        pthread_join(tid, NULL);
        printf("result = %.2f\n", s.d);
        break;
    case '*':
        ret = pthread_create(&tid, NULL, do_mul, &s);
        if (ret != 0)
        {
            errno = ret;
            perror("pthread_create fail");
            return -1;
        }
        pthread_join(tid, NULL);
        printf("result = %.2f\n", s.d);
        break;
    case '/':
        ret = pthread_create(&tid, NULL, do_div, &s);
        if (ret != 0)
        {
            errno = ret;
            perror("pthread_create fail");
            return -1;
        }
        pthread_join(tid, NULL);
        printf("result = %.2f\n", s.d);
        break;
    }

    pthread_exit(NULL);  // 主线程退出
    return 0;
}
复制代码
复制代码
#include <stdio.h> #include <pthread.h> #include <errno.h>  // 定义计算结构体 typedef struct {  float a; // 第一个操作数  float b; // 第二个操作数  char c; // 运算符 (+, -, *, /)  float d; // 计算结果 } cal_t;  // 加法线程 void *do_add(void *arg) {  cal_t *p = arg; // 接收结构体指针  p->d = p->a + p->b; // 计算结果直接存回结构体  return NULL; }  // 减法线程 void *do_sub(void *arg) {  cal_t *p = arg;  p->d = p->a - p->b;  return NULL; }  // 乘法线程 void *do_mul(void *arg) {  cal_t *p = arg;  p->d = p->a * p->b;  return NULL; }  // 除法线程 void *do_div(void *arg) {  cal_t *p = arg;  p->d = p->a / p->b; // 注意:未处理除零错误  return NULL; }  int main(int argc, const char *argv[]) {  cal_t s;  printf("Input a pression:");  // 格式化输入:如 "3.5*2.0" 会被解析为 a=3.5, c='*', b=2.0  scanf("%f%c%f", &s.a, &s.c, &s.b);   pthread_t tid;  int ret = 0;   // 根据运算符创建对应的计算线程  switch (s.c)  {  case '+':  ret = pthread_create(&tid, NULL, do_add, &s); // 传入结构体地址  if (ret != 0)  {  errno = ret;  perror("pthread_create fail");  return -1;  }  pthread_join(tid, NULL); // 等待计算完成  printf("result = %.2f\n", s.d); // 从结构体读取结果  break;  case '-':  ret = pthread_create(&tid, NULL, do_sub, &s);  if (ret != 0)  {  errno = ret;  perror("pthread_create fail");  return -1;  }  pthread_join(tid, NULL);  printf("result = %.2f\n", s.d);  break;  case '*':  ret = pthread_create(&tid, NULL, do_mul, &s);  if (ret != 0)  {  errno = ret;  perror("pthread_create fail");  return -1;  }  pthread_join(tid, NULL);  printf("result = %.2f\n", s.d);  break;  case '/':  ret = pthread_create(&tid, NULL, do_div, &s);  if (ret != 0)  {  errno = ret;  perror("pthread_create fail");  return -1;  }  pthread_join(tid, NULL);  printf("result = %.2f\n", s.d);  break;  }   pthread_exit(NULL); // 主线程退出  return 0; }

运行示例:

text

复制代码
Input a pression:3.5*2.0
result = 7.00
复制代码
复制代码
Input a pression:3.5*2.0 result = 7.00

设计亮点: 利用结构体指针实现主线程和子线程之间的数据传递------主线程负责输入和输出,子线程负责计算。通过 pthread_join 确保计算完成后才读取结果。


七、进程 vs 线程:性能对比

7.1 fork 性能测试(创建 10000 个子进程)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int i = 10000;
    pid_t pid;

    for (i = 10000; i >= 0; --i)
    {
        pid = fork();        // 创建子进程(开销大:复制页表等)
        if (pid > 0)         // 父进程
        {
            wait(NULL);      // 等待子进程结束(回收资源)
            continue;
        }
        else if (pid == 0)   // 子进程
        {
            exit(100);       // 立即退出
        }
    }
    return 0;
}

7.2 线程性能测试(创建 10000 个线程)

复制代码
#include <stdio.h>
#include <pthread.h>

// 线程函数:直接退出
void *thread(void *arg)
{
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    pthread_t tid;
    int i = 10000;

    for (; i >= 0; --i)
    {
        // 创建线程(开销小:只需分配栈)
        int ret = pthread_create(&tid, NULL, thread, NULL);
        pthread_join(tid, NULL);  // 等待并回收
    }
    return 0;
}

结论: 同样创建 10001 个执行体,线程版本的执行速度远快于 进程版本。因为 fork 需要复制整个进程的地址空间、页表、文件描述符表等,而 pthread_create 只需分配少量栈空间和管理结构。

编译线程程序时需要链接 pthread 库:gcc -o test test.c -lpthread


八、查看线程的 Linux 命令

复制代码
# 查看所有线程(LWP = Light Weight Process)
ps -eLf

# 更详细地查看线程信息
ps -eLo pid,ppid,lwp,stat,comm

输出中的 LWP 列就是线程 ID,同一进程的多个线程共享相同的 PID


九、总结速查表

9.1 线程 API 速查

函数 功能 对应进程函数
pthread_create 创建线程 fork
pthread_exit 退出线程 exit
pthread_join 等待线程结束并回收资源 wait
pthread_cancel 取消线程 kill
pthread_detach 设置线程为分离状态 ---
pthread_self 获取当前线程 ID getpid
pthread_cleanup_push 注册清理函数 atexit
pthread_cleanup_pop 弹出(并可选执行)清理函数 ---
pthread_attr_init 初始化属性对象 ---
pthread_attr_destroy 销毁属性对象 ---
pthread_attr_setdetachstate 设置分离属性 ---

9.2 线程退出方式总结

复制代码
线程结束的 4 种方式:
├── 1. pthread_exit(retval)     ← 主动退出,可携带状态值
├── 2. return retval            ← 从执行函数返回
├── 3. pthread_cancel(tid)      ← 被其他线程取消
└── 4. exit() / main() return   ← 整个进程终止(所有线程都结束)

9.3 清理函数触发条件

复制代码
pthread_cleanup_push 注册的清理函数在以下情况触发:
├── 1. pthread_cleanup_pop(非零值)   ← 主动触发
├── 2. 线程调用 pthread_exit()       ← 退出时触发
└── 3. 线程被 pthread_cancel()       ← 被取消时触发

十、注意事项与常见陷阱

    1. 线程共享资源需同步保护: 多线程操作同一变量时,需要使用互斥锁(pthread_mutex)等机制,否则会出现竞态条件。
    1. pthread_exit 不等于进程退出: 在 main 函数中调用 pthread_exit() 只是结束主线程,进程会等所有子线程结束后才退出。
    1. 返回值指针的生命周期: 线程函数返回的指针不能指向栈上的局部变量,应该指向 static 变量、全局变量或堆内存。
    1. pthread_cleanup_push/pop 必须配对: 它们是宏实现,必须在同一代码块中成对出现,否则编译报错。
    1. 分离后的线程不能再 pthread_join 对已分离的线程调用 pthread_join 会返回 EINVAL
    1. 编译时别忘了 -lpthread 使用 POSIX 线程函数库需要链接 pthread 库。

相关推荐
Nebula_g19 天前
Java实现本地Socket通信(二)
java·开发语言·socket·线程·chat
xiaoye-duck22 天前
《Linux系统编程》Linux 系统多线程(六):<线程同步与互斥>线程同步(下):POSIX 信号量与环形队列生产者消费者模型详解
linux·线程
xiaoye-duck25 天前
《Linux系统编程》Linux 系统多线程(五):<线程同步与互斥>线程互斥(上):从条件变量到生产者消费者模型详解
linux·线程
xiaoye-duck1 个月前
《Linux系统编程》Linux 系统多线程(二):进程 vs 线程资源划分 + 线程控制
linux·线程
欢醉1 个月前
一次P0 故障:微服务线程数莫名暴涨 10 倍,线程问题踩坑
springboot·线程·springcloud
是个西兰花1 个月前
Linux:深入解析Linux线程原理与实现
linux·运维·c++·线程·互斥锁
xiaoye-duck1 个月前
《Linux系统编程》Linux 系统多线程(一):线程概念(从虚拟地址空间与分页机制到优缺点解析)
linux·线程
ShineWinsu2 个月前
对于Linux:线程局部存储(TLS)和线程封装的解析
linux·c++·面试·线程·tls·线程封装·线程局部存储
ShineWinsu2 个月前
对于Linux:线程概念与分页存储管理的解析
linux·运维·服务器·面试·线程·进程·虚拟空间地址