☰
【Linux笔记】线程概念与控制
2026/10/3 12:54:10 网站建设 项目流程

一、线程概念

1.1 理解线程

通俗理解:线程是进程中的一个执行分支 (执行流),CPU 实际在跑的那条指令序列

本质理解:Linux 内核中进程和线程使用同一个数据结构task_struct来表示,所以线程本质上就是共享部分资源的"轻量级进程"

打个比方:

  • 进程像一家工厂,拥有厂房、设备、原料 (内存、文件等资源)

  • 线程像工厂里的工人,每个工人各自干活,共享工厂的一切资源

  • 工厂至少要有一个工人,所以一个进程至少有一个线程 (主线程)

  • 多招几个工人(线程),他们共用厂房原料,沟通成本极低,但一个工人操作失误弄坏原料(写坏共享内存),全厂遭殃。

1.2 进程与线程共享的资源

从内核和资源的角度:在Linux中,进程是资源分配的单位(负责管理内存和文件),线程是调度的单位(负责占用CPU)。

"线程是轻量级进程"——站在Linux内核视角里根本没有进程和线程两种东西,只有一群 task_struct,谁和谁共享同一套资源对象,谁和谁就是"同一个进程里的线程"。

在Linux下“资源”具体指以下三大类 ​ 1. 内存资源(地址空间) ​ - 进程独有:每个进程都有一张独立的页表(Page Table),映射到不同的物理内存。 ​ - 线程共享:同一个进程里的线程,共用同一张页表。(因此,线程A malloc 出一块内存,线程B 直接能用指针访问) ​ ​ 2.I/O 资源(文件描述符表) ​ - 进程独有:每个进程维护一张打开文件描述符表,记录着当前打开了哪些文件、socket(网络套接字)、管道。 ​ - 线程共享:线程间共享这张表。( 所以线程A打开 fd= 3,线程B可以直接往 fd=3 写数据) ​ ​ 3. 身份与状态资源(上下文) ​ - 进程ID(PID/PPID)、用户ID(UID/GID)、工作组(Session ID)。 ​ - 信号处置表(Signal Handlers):比如你对 SIGINT 设置了自定义回调函数。 ​ 线程共享这张表,但对于信号的状态表(Pending)线程是独立的。 ​ - 当前工作目录(cwd)和环境变量(environ)。

线程共享进程资源的核心:资源被做成独立的内核对象,线程通过"指针"指向它们,而不是把资源复制一份。

多个线程的 task_struct 里,指向资源的指针,通过引用计数的方式指向同一个对象。

  • 共享的(资源):mm、files、sighand...指向同一对象

  • 私有的(执行上下文):寄存器、程序计数器、用户栈、内核栈、TLS、errno、TID、调度状态……每个线程一份

图示理解线程资源的指向:

线程A的 task_struct 和 线程B的 task_struct | ├─ mm ────────┐ │ └─────→ 同一个 mm_struct(引用计数 = 2) (同一份页表) │ ├─ files ─────┐ │ └─────→ 同一个 files_struct(引用计数 = 2) (同一张文件描述符表) │ └─ sighand ───┐ └─────→ 同一个 sighand_struct(引用计数 = 2) (同一张信号处理表)

1.3 线程中私有的资源

1.3.1 CPU 寄存器 ---硬件上下文

当线程在运行时:这些硬件上下文数据放在 CPU 中;

当线程被切换时:内核把它们保存到该线程的内核栈(用PCB进行维护) 中,切回来时进行恢复。

- 程序计数器 PC:我执行到哪条指令了 ​ - 栈指针 SP:指向我的用户栈 ​ - 通用寄存器:计算中间的结果 ​ - 标志寄存器、浮点/向量寄存器

1.3.2 虚拟内存 ---栈

- 用户栈:局部变量、函数参数、返回地址全在这里,局部变量。 ·对于主线程的用户栈,位于虚拟地址空间的栈区,能够动态增长,在可执行程序加载时进行分配。 ·对于子线程的用户栈,位于虚拟地址空间的共享区,大小固定,由mmap进行申请 ·用户栈通过task_struct中的mm_struct结构体和vm_area_struct进行管理。 - 内核栈:线程通过系统调用进入内核时,走自己独立的内核栈。 ·在内核空间中都有的固定大小的栈。 ·内核栈通过task_struct中的stack指针进行管理 ​ - TLS(线程局部存储): 通过__thread/ thread_local 修饰的变量 和 errno 变量。 ​ ·每个线程的 fs 寄存器指向自己那一块 TLS,所以同一行代码里写 errno,各线程读写的是各自的副本。

1.3.3 task_struct ---部分字段

- LWP :线程身份ID ​ - 调度状态(运行/睡眠)、优先级、nice 值、CPU 亲和性、调度策略 ​ - 信号屏蔽字 blocked:线程选择挡住哪些信号 ​ - 线程私有的 pending 信号

1.3.4 总结

线程拥有独立的: ​ 1. 寄存器 ​ 2. 程序计数器 PC ​ 3. 线程栈 Stack ​ 4. 线程局部存储 TLS ​ 5. 调度状态
线程共享进程的: ​ 1. 代码段 ​ 2. 全局变量 ​ 3. 静态变量 ​ 4. 堆 Heap ​ 5. 文件描述符 ​ 6. Socket ​ 7. 进程资源

二、Linux 线程控制

2.2pthread_create- 创建线程

2.2.1 基础使用要求

1.头文件包含: #include <pthread.h> ​ 2.函数原型: ​ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); ​ 3.函数功能:创建出一个新的线程,产生一个新的执行流。

注:

A. 编译链接参数:pthread不是 Linux 默认链接的库,编译时需要显式添加-lpthread或-pthread参数,否则会报undefined reference to pthread_create的链接错误。

B. 错误处理规则:与常规系统调用不同,pthread_create失败时不会设置全局errno,而是直接返回对应的错误码,需要用strerror(ret)将错误码转换为可读的错误信息,不能使用perror()。

2.2.1 参数说明

1.参数一:pthread_t *thread - 指向 pthread_t 类型变量的指针,用于接收新创建线程的唯一标识符(线程 ID)。 - 后续对该线程的所有操作(如等待结束、分离、取消等)都需要通过这个 ID 进行。 2.参数二:const pthread_attr_t *attr (一般可忽略) - 栈大小:默认在 x86_64 架构下为 2MB,可通过 pthread_attr_setstacksize() 修改 - 分离状态:默认可连接(joinable),需要调用 pthread_join() 回收资源;也可设置为分离(detached),线程结束后自动释放资源 - 调度策略:如 SCHED_FIFO(实时先进先出)、SCHED_RR(实时轮转)等,默认为普通分时调度 SCHED_OTHER 3.参数三:start_routine(线程入口函数) 函数头必须按照该格式设计:void* 函数名 (void * arg) ,线程执行的入口部分。 4.参数四: void *arg (传参给线程入口函数) - 传递给 start_routine 的唯一参数,类型为 void*。 - 如果需要传递多个参数,必须将所有参数封装到结构体中,传递结构体的地址。

2.2.2 函数返回值

成功:返回 0,此时 thread 指向的变量会被设置为新线程的 ID。 失败:返回非零的错误码,thread 指向的内容未定义,不会创建新线程。 注:需要通过strerror进行输出错误信息。

函数的错误码:

错误码含义
EAGAIN系统资源不足,无法创建新线程(如超过线程数上限、内存不足等)
EINVALattr属性配置无效,或start_routine为NULL
EPERM没有权限设置attr中指定的调度策略或参数(如实时调度需要 root 权限)

2.2.3 代码演示

#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h> void *thread_run(void *args) { int cnt = 5; while (cnt--) { printf("%s starts running...\n",name); } return nullptr; } int main() { pthread_t tid; int ret = pthread_create(&tid, nullptr, thread_run, (void *)"thread -1"); if (ret != 0) { strerror(ret); } int cnt = 5; while (cnt--) { printf("Main thread running...\n"); } return 0; }

运行结果如下所示:

Main thread running... Main thread running... Main thread running... Main thread running... Main thread running... thread -1 starts running... thread -1 starts running... thread -1 starts running... thread -1 starts running... thread -1 starts running... thread -1 starts running...

2.3pthread_join-线程等待

2.3.1 基础使用条件

1.头文件包含 #include <pthread.h> 2.函数原型:int pthread_join(pthread_t thread, void **retval); 3.函数功能:同步和回收可连接(joinable)的线程资源

2.3.2 参数说明

1.参数一:pthread_t thread - 目标线程的 ID,即 pthread_create 第一个输出参数获得的值。 - 必须是当前进程中有效的、尚未被 join 且未分离的线程 ID。 2.参数二:void **retval - 指向 void* 指针的二级指针,用于接收目标线程通过 return 或 pthread_exit() 传递的返回值。 - 如果不需要获取返回值,传 NULL 即可。 - 如果目标线程是被 pthread_cancel() 取消的,*retval 会被设置为特殊常量 PTHREAD_CANCELED。

注意:绝对不要将局部变量的地址作为返回值传出!因为线程结束后其栈帧已被销毁,该地址变为悬空指针,访问它将导致未定义行为,应使用堆内存、全局变量或静态变量来传递复杂数据。

#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> void* worker(void* arg) { int input = *(int*)arg; int* result = malloc(sizeof(int)); // 堆上分配,安全 *result = input * input; return result; } int main() { pthread_t tid; int val = 7; pthread_create(&tid, NULL, worker, &val); void* ret; int rc = pthread_join(tid, &ret); if (rc != 0) { printf("join failed: %s\n", strerror(rc)); return 1; } printf("7^2 = %d\n", *(int*)ret); free(ret); // 记得释放堆内存 return 0; }

2.4pthread_detach-线程分离

2.4.1 基础使用条件

1.头文件包含: #include <pthread.h> 2.函数原型:int pthread_detach(pthread_t thread); 3.函数功能:将指定线程标记为"分离状态(detached)",分离线程终止时,系统自动回收其资源(栈、线程描述符等),无需也不允许其他线程调用 pthread_join。

2.4.2 detach后的线程状态

操作系统detach线程的执行过程 1. 立即释放线程栈内存(通常几 MB) 2. 释放线程控制块(TCB) 3. 丢弃返回值(即使线程函数 return 了非 NULL 值,也无处接收) 4. 不保留任何可供 join 的状态

2.4.3 detach的两种时机

A. 运行时 detach
pthread_t tid; pthread_create(&tid, NULL, worker, arg); pthread_detach(tid); // 创建后分离
B. 创建前 detach
pthread_attr_t attr;//线程属性 pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_t tid; pthread_create(&tid, &attr, worker, arg); // 出生即分离 pthread_attr_destroy(&attr); // 属性对象用完必须销毁,否则泄漏
C. detach 时间线分析
方式A的时间线: create ──────────────── detach ↑ 危险窗口 ↑ 线程可能在此区间内已经终止! 如果线程在 pthread_create 和 pthread_detach 之间就执行完毕并退出了,而系统又恰好立即回收了其资源,那pthread_detach(tid) 操作的就是一个无效的线程标识符,属于未定义行为。
方式B的时间线: create(DETACHED) ───→ 线程从第一刻起就是分离状态 不存在竞态窗口

2.4.4 detach 的约束

A. 绝不能对分离线程调用 join
pthread_detach(tid); pthread_join(tid, NULL); // 明确规定这是未定义行为
B. 不能获取返回值
void* worker(void* arg) { return some_result; // 如果线程是 detached,这个返回值被丢弃 }
C. 参数生命周期必须自给自足
// 经典错误 void * worker(void * args) { //...执行任务 return NULL; } void spawn_task(int id) { int local_id = id; pthread_create(&tid, &attr, worker, &local_id); // 传栈变量地址 // spawn_task 返回后 local_id 销毁 // 分离线程可能还在运行,此时读到悬空指针 }

在pthread_join模式下,主线程通过等待隐式延长了参数的生命周期。

detach 模式下没有这个安全机制,spawn_task函数结束后,局部变量销毁,导致线程中的指针悬空。

2.5 线程终止

线程终止:是线程生命周期的最后一个环节,也是资源回收的前置条件。

POSIX 提供了三种终止方式,它们的语义、副作用和适用场景各不相同。

2.5.1 线程执行return

基本语义:线程入口函数void*(*)(void*)执行return语句,是最自然的终止方式。

void* worker(void* arg) { int id = *(int*)arg; printf("Thread %d doing work...\n", id); // 正常执行完毕,return 一个值 return (void*)(long)id; // 将整数转为 void* 返回 }

关键细节

细节一:return 的值就是pthread_join能取到的值

void* worker(void* arg) { int id = *(int*)arg; printf("Thread %d doing work...\n", id); // 正常执行完毕,return 一个值 return (void*)(long)id; // 将整数转为 void* 返回 } void* retval; pthread_join(tid, &retval); //二级指针进行获取 printf("Thread returned: %ld\n", (long)retval);

细节 2:return NULL 表示"无返回值",不是"失败"

void* worker(void* arg) { // 做一些事情... return NULL; // 正常终止,没有需要传递的结果 }

NULL和PTHREAD_CANCELED是不同的,join 可以通过这个区分判断线程是"自然死亡"还是"被杀死"。

  • NULL表示线程自行决定正常结束;

  • PTHREAD_CANCELED是系统标记,表示线程被外部取消。

细节 3:绝不能返回局部变量的地址

//错误做法: void* worker(void* arg) { char buf[128]; snprintf(buf, sizeof(buf), "result_%d", *(int*)arg); return buf; // 致命错误!buf 在 return 后栈帧销毁 } //正确做法: // 方案A:堆分配 char* buf = malloc(128); snprintf(buf, 128, "result_%d", *(int*)arg); return buf; // 调用方负责 free // 方案B:静态/全局变量(不推荐,非线程安全) static char buf[128]; // 多线程同时 return 会覆盖 // 方案C:返回整数值(最安全) return (void*)(long)some_int;

细节 4:return 只终止当前线程,不影响其他线程

void* worker(void* arg) { return NULL; // 仅本线程结束 // main 线程和其他子线程继续运行 }

细节 5:C++ 中 return 会触发栈上对象的析构

void* worker(void* arg) { std::string s = "hello"; std::lock_guard<std::mutex> lock(mtx); // ... 做一些事情 ... return NULL; // s 的析构函数被调用 // lock 的析构函数被调用(mutex 被解锁) // 所有 RAII 对象正常清理 }

2.5.2 线程执行pthread_exit

函数原型:

头文件包含:#include <pthread.h> 函数原型:void pthread_exit(void *retval);

核心用法:显式终止调用它的线程。

retval的含义与return完全一致——都是传递给pthread_join的返回值。

void* worker(void* arg) { int id = *(int*)arg; if (some_error_condition) { fprintf(stderr, "Thread %d encountered error, exiting early.\n", id); pthread_exit((void*)-1); // 提前终止,不执行后续代码 } // 正常逻辑... return (void*)(long)id; }

细节分析:

POSIX 标准的规定:pthread_exit应当触发 C++ 栈展开(stack unwinding),即调用析构函数。但实际行为依赖编译器和平台:

  • Linux glibc + g++:通常正确触发析构(通过内部__cleanup机制)

  • 某些嵌入式/旧编译器:可能直接longjmp,跳过析构。

  • C++ 标准本身:不承认pthread_exit的存在,行为是实现定义的。

void* worker(void* arg) { std::string s = "important data"; std::lock_guard<std::mutex> lock(mtx); helper_function(); // 内部调用了 pthread_exit // 问题:s 和 lock 的析构函数是否被调用? } void helper_function() { // ... 一些逻辑 ... pthread_exit(NULL); // 从辅助函数中终止线程 }

2.6pthread库

2.6.1 线程库的本质定位

线程库的本质:Linux 内核本身没有"线程"这个概念,线程是用户态库(pthread)对内核"轻量级进程"机制的封装。

内核提供"底层结构"(轻量级进程task_struct+futex),pthread库负责"封装"成标准化的线程接口。

┌─────────────────────────────────┐ │ pthread │ 用户空间: │ ┌──────────┐ ┌──────────┐ │ │ │ struct │ │ struct │ │ ← 线程控制块(TCB):管理线程属性、栈地址等 │ │ pthread │ │ pthread │ │ │ │ (TCB) │ │ (TCB) │ │ │ └────┬─────┘ └────┬─────┘ │ │ │ 线程栈 │ 线程栈 │ │ ▼ ▼ │ ├───────┼─────────────┼───────────┤ │ │ clone() │ clone() │ ← 系统调用边界 ├───────┼─────────────┼───────────┤ │ ▼ ▼ │ 内核空间: │ ┌───────────┐ ┌────────────┐ │ │ │task_struct│ │task_struct │ │ ← 内核调度实体(LWP):cpu的调度单位 │ │ (LWP) │ │ (LWP) │ │ │ └───────────┘ └────────────┘ │ │ │ └─────────────────────────────────┘

2.6.2 线程地址空间布局

线程地址空间布局图:

┌─────────────────────┐ Highest addresses │ │ | Kernel space | | | ┌─────────────────────┐ | Stack | | | │ Main thread stack │ ← 主线程栈 ├─────────────────────┤ │ mmap shared area │ ← TCBs + thread stacks for other threads are here │ ┌───────────────┐ │ 其他线程的 TCB + 线程栈都在这里 │ │ TCB (Thread 2)│ │ │ │ Stack of Thd 2│ │ │ ├───────────────┤ │ │ │ TCB (Thread 3)│ │ │ │ Stack of Thd 3│ │ │ └───────────────┘ │ | ... │ ├─────────────────────┤ │ Heap (shared) │ ←堆区 ├─────────────────────┤ │ Data segment │ ←数据段 ├─────────────────────┤ | Code segment | ←代码段 └─────────────────────┘ Low addresses 低地址

2.6.3 内核空间管理线程

Linux 内核对进程和线程都用task_struct表示,每个线程在内核中都有一个独立的task_struct,这就是一个轻量级进(LWP)。

task_struct中的关键字段:

struct task_struct { //线程的ID pid_t pid; // 内核线程 ID(每个 LWP 唯一) pid_t tgid; // 线程组 ID(同进程所有线程共享) // 管理线程 struct task_struct *group_leader; // 指向主线程 struct list_head thread_group; // 线程组链表 struct mm_struct *mm; // 地址空间描述符(同组线程共享) // 线程的上下文 // ... 寄存器、调度优先级、状态等 };
  • pid:内核视角的"进程 ID",对 LWP 来说就是线程 ID(通过gettid()获取)

  • tgid:线程组 ID,等于主线程的pid(getpid()返回的就是tgid)

  • mm:指向虚拟内存描述符,同组线程共享同一个mm,因此共享地址空间

2.6.4 用户空间管理线程

┌──────────────────────────────┐ 高地址 │ struct pthread(TCB) │ ← pthread_t 就是这个结构体的首地址 ├──────────────────────────────┤ │ 线程局部存储(TLS) │ ← __thread 变量的副本 ├──────────────────────────────┤ │ │ │ 线程栈(默认 8MB) │ ← 向下增长 │ │ └──────────────────────────────┘ 低地址 High addresses 高地址 ┌──────────────────────────────┐ │ struct pthread (TCB) │ ← 线程描述表:pthread_t 就是这个结构体的首地址 ├──────────────────────────────┤ │ TLS │ ← 线程局部存储: 存储__thread 变量的副本 ├──────────────────────────────┤ │ │ │ Thread stack │ ← 线程栈: 向下增长 │ (default 8MB) │ └──────────────────────────────┘ Low addresses 低地址
A.struct pthread-TCB
struct pthread { /* ---- 标识信息 ---- */ struct pthread *self; // ★指向自身(用于 %fs:0 快速定位) pid_t tid; // ★内核分配的 LWP ID(gettid 的值) pid_t pid; // ★进程 ID(tgid) /* ---- 线程栈信息 ---- */ void *stackblock; // ★栈内存的起始地址 size_t stackblock_size; // 栈大小(默认 8MB) void *guardaddr; // 栈底保护页地址(防止栈溢出) size_t guardsize; // 保护页大小(默认 4KB) /* ---- 生命周期状态 ---- */ int cancel_state; // 取消状态(是否允许被取消) int cancel_type; // 取消类型(延迟/异步) int canceled; // 是否已被取消 int detached; // 是否已 detach int joinid; // 谁在 join 我 /* ---- 同步相关 ---- */ struct pthread_mutex *mutex_list; // 持有的互斥锁链表 futex_t *robust_list; // 健壮互斥锁列表 /* ---- 清理与退出 ---- */ struct _pthread_cleanup_buffer *cleanup; // 清理处理器链表 void *(*start_routine)(void *); // 线程入口函数 void *arg; // 入口函数参数 void *result; // ★线程返回值(pthread_exit 的参数) /* ---- 信号相关 ---- */ sigset_t sigmask; // 线程信号掩码 sigset_t sigpending; // 挂起信号集 /* ---- TLS 相关 ---- */ void *specific[LEVEL1_SIZE]; // pthread_key_create 的动态 TLS 数组 dtv_t *dtv; // 动态线程向量(Dynamic Thread Vector) /* ---- errno ---- */ int err_no; // 本线程的 errno(通过 TLS 访问) /* ---- 调度信息 ---- */ int schedpolicy; // 调度策略 int schedparam; // 调度参数 int flags; // 线程标志位 /* ... 更多字段 ... */ };

关键字段:

字段作用
self放在 TCB 的第一个位置,%fs:0直接读到 TCB 自身地址。
tid创建时由内核通过CLONE_CHILD_SETTID写入,是用户态访问内核 LWP 的唯一凭证
stackblock记录栈的起始地址,线程退出时据此释放内存
dtv动态线程向量,管理动态链接库中声明的__thread变量
specific[]pthread_key_create()创建的动态 TLS 槽位
cleanuppthread_cleanup_push()注册的清理函数链表
resultpthread_exit()或线程函数返回值,供pthread_join()获取
B. 线程局部存储 -TLS

TLS解决的问题:全局变量和静态变量在所有线程间共享,但是很多场景需要"看起来像全局变量,实际上每个线程各有一份"的数据。

例如:errno、日志缓冲区、线程私有缓存等。

关键字:__thread和thread_local

// C 语言(GCC 扩展) __thread int counter = 0; //每个线程都能看见,但是y独立的一份 // C++11 标准 thread_local std::string buffer;
C. 线程栈 -Thread stack

线程栈存放的内容如下所示:

┌─────────────────────────────────────┐ Higher addresses │ Function arguments │ ← 函数参数 ├─────────────────────────────────────┤ │ Return address (pushed by call) │ ← 返回地址(call 指令压入) ├─────────────────────────────────────┤ │ Saved registers (RBP, RBX, etc.) │ ← 保存的寄存器(RBP、RBX 等) ├─────────────────────────────────────┤ │ Local variables (arrays, structs) │ ← 局部变量(数组、结构体等) ├─────────────────────────────────────┤ │ Temporary values (intermediate) │ ← 临时值(函数调用的中间结果) └─────────────────────────────────────┘

线程栈的介绍:

- 用户栈:局部变量、函数参数、返回地址全在这里,局部变量。 ·对于主线程的用户栈,位于虚拟地址空间的栈区,能够动态增长,在可执行程序加载时进行分配。 ·对于子线程的用户栈,位于虚拟地址空间的共享区,大小固定,由mmap进行申请 ·用户栈通过task_struct中的mm_struct结构体和vm_area_struct进行管理。 - 内核栈:线程通过系统调用进入内核时,走自己独立的内核栈。 ·在内核空间中都有的固定大小的栈。 ·内核栈通过task_struct中的stack指针进行管理

2.6.5 线程的完整流程

A.线程创建的完整流程
用户空间 内核空间 │ │ │ 1. mmap() 分配线程栈 + TLS │ │ 2. 初始化 TCB (struct pthread) │ │ 3. 调用 clone() 系统调用 ──────────→│ │ flags: | | CLONE_VM | 4. 创建 task_struct │ CLONE_FILES │ 5. 根据 flags 共享资源: │ CLONE_SIGHAND │ - CLONE_VM → 共享 mm(地址空间) │ ... │ - CLONE_FILES → 共享 files_struct(文件描述符表) │ │ - CLONE_SIGHAND → 共享 signal_struct(信号处理) | │ ... | │ │ │ 6. 设置 tgid = 父线程 tgid │ │ 7. 设置 group_leader │ │ 8. 加入调度队列 │ ←──────── 返回新线程的 LWP ID ──── │ │ 9. 记录 TCB 与 LWP 的映射 │ │ │ │ │ 10. 新线程被调度后开始执行 start_routine │ │
B.线程销毁的完整流程
用户空间 内核空间 │ │ │ 1. 线程函数返回,调用pthread_exit() │ │ │ │ 2. 执行清理处理器 │ │ 3. 调用 exit() 系统调用 ──────────→ │ │ │ 4. 内核释放 task_struct │ │ 5. 如果设置了 CLONE_CHILD_CLEARTID, │ │ 清零 TCB 中的 tid 字段 │ │ │ ←──────────────────────────────── │ 6. futex 唤醒等待 join 的线程 | 7. join 线程被唤醒,获取返回值 | │ 8. 回收线程栈和 TCB(mmap 释放) │

2.7 thread的封装

#pragma once #include <iostream> #include <string> #include <functional> #include <pthread.h> #include <cstring> #include <cstdlib> // 基于 C 接口 (pthread) 的 C++ 线程封装 namespace ThreadModule { // 已绑参的可调用对象类型: 无参无返回 using func_t = std::function<void()>; // 内部上下文: 承载可调用对象 + 线程名 // 独立分配在堆上, 是为了把生命周期与 Thread 对象解耦 // (线程可能在 Thread 对象析构后仍在运行, 不能让回调引用悬空) class ThreadData { friend class Thread; // 须带 class: 否则 Thread 尚未声明, 会被当成友元函数声明 public: ThreadData(const func_t &func, const std::string &name) : _func(func), _name(name) { } private: func_t _func; std::string _name; }; // 线程状态: 防止非法操作 (重复 join / join 已 detach 的线程等) enum class ThreadStatus { NEW, // 已构造, 未启动 RUNNING, // 运行中 DETACHED, // 已分离 JOINED // 已回收 }; // 线程自动编号: inline 函数 + static 局部变量 // C++11 下 inline 函数的 static 局部量跨编译单元共享, 头文件安全 inline int NextThreadId() { static int id = 1; return id++; } class Thread { public: // 接受任意可调用对象 + 参数, 用 std::bind 绑参后转为 std::function<void()> template <typename F, typename... Args> Thread(const std::string &name, F &&f, Args &&...args) { _name = name + "-" + std::to_string(NextThreadId()); _status = ThreadStatus::NEW; _td = new ThreadData( std::bind(std::forward<F>(f), std::forward<Args>(args)...), _name); } ~Thread() { // 析构策略 B : 仍在运行就自动 detach, 保证不泄漏 if (_status == ThreadStatus::RUNNING) { pthread_detach(_tid); // 不断言, 容错 (线程可能已结束) } // 从未启动: 跳板不会运行, 自己清理 _td if (_status == ThreadStatus::NEW && _td != nullptr) { delete _td; _td = nullptr; } // 已启动 (RUNNING/DETACHED/JOINED): _td 由跳板结束时释放, 此处不动 } // 禁用拷贝: 一个 pthread_t 不能被两个对象管理 (double join / double free) Thread(const Thread &) = delete; Thread &operator=(const Thread &) = delete; // 启动线程 (构造与启动分离, 便于统一管理一批线程) void start() { if (_status != ThreadStatus::NEW) { std::cerr << "[Thread:" << _name << "] already started\n"; return; } int n = pthread_create(&_tid, nullptr, start_routine, _td); if (n != 0) { std::cerr << "pthread_create error: " << strerror(n) << "\n"; std::abort(); } _status = ThreadStatus::RUNNING; } // 等待回收 void join() { if (_status != ThreadStatus::RUNNING) { std::cerr << "[Thread:" << _name << "] cannot join (status not RUNNING)\n"; return; } int n = pthread_join(_tid, nullptr); if (n != 0) { std::cerr << "pthread_join error: " << strerror(n) << "\n"; std::abort(); } _status = ThreadStatus::JOINED; _td = nullptr; // _td 已被跳板释放, 仅置空标记 } // 分离 void detach() { if (_status != ThreadStatus::RUNNING) { std::cerr << "[Thread:" << _name << "] cannot detach (status not RUNNING)\n"; return; } int n = pthread_detach(_tid); if (n != 0) { std::cerr << "pthread_detach error: " << strerror(n) << "\n"; std::abort(); } _status = ThreadStatus::DETACHED; _td = nullptr; // _td 由跳板结束时释放 } const std::string &name() const { return _name; } bool isRunning() const { return _status == ThreadStatus::RUNNING; } private: // 跳板: 签名必须匹配 pthread 要求的 void*(*)(void*) // 由于非 static 成员函数隐含 this, 签名不匹配, 因此必须用 static static void *start_routine(void *arg) { ThreadData *td = static_cast<ThreadData *>(arg); td->_func(); // 执行用户回调 delete td; // 跳板作为最终消费者, 释放上下文 return nullptr; } private: pthread_t _tid; std::string _name; // 自己保留一份, name() 在 join/detach 后仍可用 ThreadStatus _status; ThreadData *_td = nullptr; }; } // namespace ThreadModule

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询