二、线程操作

注意

使用 pthread 系列函数编译时,必须手动链接线程库:

bash 复制代码
gcc test.c -o test -pthread

不加 -pthread 会出现编译 / 运行异常。

为什么必须加 -pthread

-pthread 不只是单纯链接 -lpthread,它同时做两件事:

链接阶段:链接 libpthread 线程库,提供 pthread_create / pthread_self / pthread_exit 等函数的实现;

编译预处理阶段:定义宏、调整线程安全的标准库行为(如 errno 线程局部存储、锁实现)。

线程操作

(1) pthread_create 函数

1. 函数介绍

1. 函数原型

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

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

2. 返回值

  • 成功:返回 0;
  • 失败:返回对应错误编号 (不是之前说的 errno ,获取错误号信息 strerror),*thread 数据无效。

3. 参数

  • pthread_t *thread
    输出参数:创建成功后,新线程 ID 会存入该指针指向内存;创建失败时,*thread 内容未定义。
  • const pthread_attr_t *attr
    线程属性结构体指针:
    • 非 NULL:使用结构体中自定义属性创建线程,结构体需先用 pthread_attr_init() 初始化;
    • NULL:采用系统默认线程属性。
  • void *(*start_routine)(void *)
    线程入口函数,线程启动后执行该函数,返回值为 void*。
  • void *arg
    传递给入口函数 start_routine 的唯一参数。

4. 作用

在调用进程中创建并启动一条新线程。

2. 样例 (创建一个子线程)

准备一个 pthread_create.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

void * handle(void * arg)
{
    printf("子线程已创建...\n");
    printf("参数arg: %d\n", *(int *)arg);

    return NULL;
}

int main()
{
    // 创建新线程
    pthread_t thread;
    int num = 10;

    int tid = pthread_create(&thread, NULL, handle, (void *)&num);
    if(tid != 0)
    {
        char * str = strerror(tid);
        printf("%s\n", str);
    }

    for(int i=0; i < 3; i++)
    {
        printf("pid: %d, i: %d\n", getpid(), i);
    }

    sleep(1);

    return 0;
}

注意上面的代码部分只有 handle 函数部分是子线程独立运行的代码段。

(2) pthread_self 函数

1. 函数介绍

1. 函数原型

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

pthread_t pthread_self(void);

2. 参数:无参数。

3. 返回值

该函数调用永远成功,返回调用者线程的 ID(类型为 pthread_t)。

4. 作用

获取当前调用进程的 ID 。

(3) pthread_exit 函数

1. 函数介绍

1. 函数原型

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

void pthread_exit(void *retval);

2. 参数

  • retval:线程退出返回值指针
    • 作用:存放线程结束后要传递给其他线程的返回数据;
    • 接收方式:其他线程调用 pthread_join(&tid, &res)res 会接收该指针。
    • 取值规则:
      • 若不需要返回数据,传 NULL;
      • 若要传递数据,不能传入栈局部变量地址(线程销毁后栈会释放,内存失效),建议传全局变量 / 堆内存地址

3. 返回值

函数无返回值,调用后当前线程立刻终止,代码不会回到调用 pthread_exit 的下一行继续执行。

4. 作用

终止一个线程,在哪个线程中调用,就表示终止哪个线程。

2. 样例

创建一个 pthread_exit.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

void * rollback(void * arg)
{
    printf("child thread id : %ld\n", pthread_self());

    return NULL;
}

int main()
{
    // 创建新线程
    pthread_t tid;
    int arg = 10;

    int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程
    for(int i = 1; i < 3; i++)
    {
        printf("i = %d\n", i);
    }

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());

    // 终止主线程
    pthread_exit(NULL);

    printf("main thread die...\n");
    
    return 0;
}

可以看到 "main thread die..." 并没有输出,因此可以得到终结线程后的代码将不会执行。

(4) pthread_join 函数

1. 函数介绍

1. 函数原型

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

int pthread_join(pthread_t thread, void **retval);

2. 参数

  • pthread_t thread
    • 待等待的目标线程 ID;
    • 限制:该线程必须是 joinable(默认新建线程都是 joinable,调用 pthread_detach 后不可 join)。
  • void **retval
    • 输出型二级指针,用于接收目标线程的退出返回值;
    • 传 NULL:代表不需要接收线程退出结果;
    • 非空:会写入 pthread_exit() 传入的指针,线程被取消则写入 PTHREAD_CANCELED

3. 返回值

  • 成功:返回 0;
  • 失败:返回对应错误编号。

4. 作用

该函数会阻塞等待参数 thread 指定的线程终止。

2. 样例

创建一个 pthread_join.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

int num = 10;  //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收

void * rollback(void * arg)
{
    printf("child thread id : %ld\n", pthread_self());

    sleep(3);  // 测试pthread_join是否阻塞

    pthread_exit((void *)&num);  // 等同于(void *)&num
}

int main()
{
    // 创建新线程
    pthread_t tid;
    int arg = 10;

    int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程
    for(int i = 1; i < 3; i++)
    {
        printf("i = %d\n", i);
    }

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());

    // 阻塞等待回收子线程
    void * thread_val;
    pthread_join(tid, &thread_val);
    printf("main thread rece: %d\n", *(int *)thread_val);

    // 终止主线程
    pthread_exit(NULL);

    printf("main thread die...\n");

    return 0;
}

(5) pthread_detach 函数

1. 函数介绍

1. 函数原型

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

int pthread_detach(pthread_t thread);

2. 参数

  • pthread_t thread
    • 目标线程 ID,指定要设置为分离态的线程;
    • 限制:该线程当前必须是可汇合 (joinable) 状态,不能已经被分离。

3. 返回值

  • 成功:返回 0;
  • 失败:返回对应错误编号。

4. 作用

  • 修改线程属性为分离态,改变资源回收规则
    默认新建线程是 joinable,退出后资源残留,必须 pthread_join 回收;
    detach 之后线程一旦结束,操作系统自动释放线程栈、线程管理结构体,不会内存泄漏。
  • 解除 "必须等待" 的限制
    主线程无需阻塞等待该线程执行完毕,二者完全异步运行。

2. 样例

创建一个 pthread_detach.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

int num = 10;  //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收

void * rollback(void * arg)
{
    printf("child thread id : %ld\n", pthread_self());

    sleep(3);  // 测试pthread_detach是否阻塞

    return (void *)&num;
}

int main()
{
    // 创建新线程
    pthread_t tid;
    int arg = 10;

    int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
    if(ret != 0) 
    {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程
    for(int i = 1; i < 3; i++)
    {
        printf("i = %d\n", i);
    }

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());

    // 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
    ret = pthread_detach(tid);
    if(ret != 0) 
    {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 设置分离后,对分离的子线程进行连接 pthread_join()
    void * thread_val;
    ret = pthread_join(tid, &thread_val);
    if(ret != 0) 
    {
        char * errstr = strerror(ret);
        printf("error2 : %s\n", errstr);
    }

    printf("main thread rece: %d\n", *(int *)thread_val);

    // 终止主线程
    pthread_exit(NULL);

    printf("main thread die...\n");

    return 0;
}

可以看到将线程分离后再次进行连接将会错误。

(6) pthread_cancel 函数

1. 函数介绍

1. 函数原型

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

int pthread_cancel(pthread_t thread);

2. 参数

  • pthread_t thread
    • 目标线程 ID,代表要发送取消信号的线程;
    • 支持对自身线程、其他线程发送取消请求。

3. 返回值

  • 成功:返回 0;
  • 失败:返回对应错误编号。

4. 作用

pthread_cancel() 函数向 thread 指定的目标线程发送取消请求。

目标线程是否响应、何时响应这个取消请求,由该线程自身的两个属性控制:state (取消使能状态) 和 type (取消类型)。

5. 两大关键控制属性(目标线程自身控制是否响应取消)

① 取消使能状态 pthread_setcancelstate 函数设置 state

  • PTHREAD_CANCEL_ENABLE(默认):接收并处理取消请求;
  • PTHREAD_CANCEL_DISABLE:忽略取消请求,请求排队暂存,直到重新开启。

② 取消类型 pthread_setcanceltype 函数设置 type

  • PTHREAD_CANCEL_DEFERRED(默认,延迟取消)
    不会立刻终止,仅在线程调用取消点函数(read/write/pthread_join/sleep 等系统 IO、同步函数)时才触发取消流程;
    业务计算循环、纯 CPU 运算代码段不会被中途打断,安全。
  • PTHREAD_CANCEL_ASYNCHRONOUS(异步取消)
    线程任意代码位置都可能被随时终止,风险极高,极易造成资源泄漏、死锁,极少使用。

6. 线程被取消后的固定执行流程

  1. 逆序执行所有清理函数 pthread_cleanup_push
  2. 执行线程私有数据 TSD 析构函数;
  3. 线程正式终止退出。

2. 样例

创建一个pthread_cancel.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

int num = 10;  //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收

void * rollback(void * arg)
{
    printf("child thread id : %ld\n", pthread_self());

    sleep(1);

    for(int i = 1; i < 100; i++)
    {
        printf("child pthread i: %d\n", i);
    }

    return (void *)&num;
}

int main()
{
    // 创建新线程
    pthread_t tid;
    int arg = 10;

    int ret = pthread_create(&tid, NULL, rollback, (void *)&arg);
    if(ret != 0) 
    {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
    pthread_detach(tid);

    // 终止指定子进程
    pthread_cancel(tid);

    for(int i = 1; i < 3; i++)
    {
        printf("i = %d\n", i);
    }

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());

    // 终止主线程
    pthread_exit(NULL);

    printf("main thread die...\n");

    return 0;
}

可以看到子线程并没有输出循环体的内容就被父进程给终止了。这是因为 sleep 函数本身是取消点函数,当取消请求队列中有取消信号则会终止该线程。

线程属性

线程属性类型 pthread_attr_t

(1) 初始化、释放线程属性资源

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

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);

描述

pthread_attr_init() 函数会使用默认值初始化 attr 指向的线程属性对象。调用该函数后,可以通过配套系列函数单独修改属性对象里的各项配置,之后该属性对象可以重复用于一次或多次 pthread_create() 创建线程。

对一个已经初始化完成 的属性对象再次调用 pthread_attr_init(),程序行为属于未定义。

当线程属性对象不再使用时,应当调用 pthread_attr_destroy() 销毁释放内部资源。销毁属性对象不会对以往依靠该属性创建出的线程产生任何影响。

属性对象被销毁后,可以再次调用 pthread_attr_init() 重新初始化复用;对已经销毁、未重新初始化的属性对象做任何其他操作,结果都是未定义行为。

返回值

两个函数调用成功均返回 0;执行失败时返回非 0 错误码。

(2) 设置、获取线程属性

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

int pthread_attr_getdetachstate(const pthread_attr_t *attr,int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);

描述

pthread_attr_setdetachstate() 函数用于设置线程属性对象 attr 的分离状态属性,参数 detachstate 指定目标状态。该属性决定了使用此属性对象创建出的线程,默认是可汇合(joinable) 还是分离(detached) 状态。

detachstate 仅支持以下两个宏取值:

  1. PTHREAD_CREATE_DETACHED
    使用该属性创建的线程,直接为分离态。
  2. PTHREAD_CREATE_JOINABLE
    使用该属性创建的线程,为可汇合态。

刚通过 pthread_attr_init() 初始化完成的线程属性对象,分离状态默认值 PTHREAD_CREATE_JOINABLE

pthread_attr_getdetachstate() 函数会将属性对象 attr 中存储的分离状态,存入 detachstate 指针指向的内存中返回。

返回值:

两个函数调用成功均返回 0;执行失败时,返回非 0 的错误码。

(3) 样例

创建一个 pthread_attr.c 文件

c 复制代码
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>

int num = 10;  //这里一定要用全局变量(存在数据段)或者堆内存,线程结束栈内存会被回收

void * rollback(void * arg)
{
    printf("child thread id : %ld\n", pthread_self());

    sleep(1);

    return (void *)&num;
}

int main()
{
    // 设置线程属性
    pthread_attr_t attr;

    pthread_attr_init(&attr);  // 初始化
    int ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);  // 设置由该属性创建的线程为分离态
    if(ret != 0)
    {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 查看新建的属性
    int detachstate;
    pthread_attr_getdetachstate(&attr, &detachstate);
    if(detachstate == PTHREAD_CREATE_DETACHED)
    {
        printf("新建属性: detached state\n");
    }
    else if(detachstate == PTHREAD_CREATE_JOINABLE)
    {
        printf("新建属性: joinable state\n");
    }

    // 创建新线程
    pthread_t tid;
    int arg = 10;

    ret = pthread_create(&tid, &attr, rollback, (void *)&arg);  // 创建子线程的同时设置属性
    if(ret != 0) 
    {
        char * errstr = strerror(ret);
        printf("error2 : %s\n", errstr);
    }

    printf("tid: %ld, main thread id: %ld\n", tid, pthread_self());

    pthread_attr_destroy(&attr);  // 释放线程属性资源

    // 终止主线程
    pthread_exit(NULL);

    printf("main thread die...\n");

    return 0;
}
相关推荐
mounter6252 小时前
探索未来 AI 算力网络的基石:从传统 RoCE 走向 SRv6 驱动的弹性弹性网络(解析 Netdev 0x1A 创新实践)
linux·网络·人工智能·linux kernel·kernel·rdma·rocev2
x_x-F2 小时前
深入浅出 Linux 网络核心:sk_buff 的内存与指针流转
linux·运维·网络
潘正翔12 小时前
docker核心概念
linux·运维·服务器·docker·容器·centos·运维开发
weigangwin12 小时前
agent-workspace-linux 不是远程桌面:Linux AI 工作区的隔离边界验收
linux·xvfb·mcp·桌面隔离·权限边界·bubblewrap·agent-workspace
Dovis(誓平步青云)14 小时前
远程办公软件文件传输实测:6 款工具的速度、稳定性和办公体验对比
linux·运维·服务器·后端·生成对抗网络
Java小白笔记14 小时前
Docker 安装配置完全指南:MacOS 、Windows、Linux环境下的安装、配置与验证
linux·macos·docker
qetfw16 小时前
CentOS 7 搭建 LDAP 目录服务
linux·运维·centos
ALINX技术博客17 小时前
【黑金云课堂】FPGA技术教程Linux开发:系统进阶-PS DMA
linux·fpga开发
IT方大同17 小时前
linux简介
linux·运维·服务器