一、线程概念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可以直接往 fd3 写数据 ​ ​ 3. 身份与状态资源上下文 ​ - 进程IDPID/PPID、用户IDUID/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为NULLEPERM没有权限设置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. 运行时 detachpthread_t tid; pthread_create(tid, NULL, worker, arg); pthread_detach(tid); // 创建后分离B. 创建前 detachpthread_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. 绝不能对分离线程调用 joinpthread_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);细节 2return 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;细节 4return 只终止当前线程不影响其他线程void* worker(void* arg) { return NULL; // 仅本线程结束 // main 线程和其他子线程继续运行 }细节 5C 中 return 会触发栈上对象的析构void* worker(void* arg) { std::string s hello; std::lock_guardstd::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_guardstd::mutex lock(mtx); helper_function(); // 内部调用了 pthread_exit // 问题s 和 lock 的析构函数是否被调用 } void helper_function() { // ... 一些逻辑 ... pthread_exit(NULL); // 从辅助函数中终止线程 }2.6pthread库2.6.1 线程库的本质定位线程库的本质Linux 内核本身没有线程这个概念线程是用户态库pthread对内核轻量级进程机制的封装。内核提供底层结构轻量级进程task_structfutexpthread库负责封装成标准化的线程接口。┌─────────────────────────────────┐ │ pthread │ 用户空间: │ ┌──────────┐ ┌──────────┐ │ │ │ struct │ │ struct │ │ ← 线程控制块TCB管理线程属性、栈地址等 │ │ pthread │ │ pthread │ │ │ │ (TCB) │ │ (TCB) │ │ │ └────┬─────┘ └────┬─────┘ │ │ │ 线程栈 │ 线程栈 │ │ ▼ ▼ │ ├───────┼─────────────┼───────────┤ │ │ clone() │ clone() │ ← 系统调用边界 ├───────┼─────────────┼───────────┤ │ ▼ ▼ │ 内核空间: │ ┌───────────┐ ┌────────────┐ │ │ │task_struct│ │task_struct │ │ ← 内核调度实体LWPcpu的调度单位 │ │ (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等于主线程的pidgetpid()返回的就是tgidmm指向虚拟内存描述符同组线程共享同一个mm因此共享地址空间2.6.4 用户空间管理线程┌──────────────────────────────┐ 高地址 │ struct pthreadTCB │ ← pthread_t 就是这个结构体的首地址 ├──────────────────────────────┤ │ 线程局部存储TLS │ ← __thread 变量的副本 ├──────────────────────────────┤ │ │ │ 线程栈默认 8MB │ ← 向下增长 │ │ └──────────────────────────────┘ 低地址 High addresses 高地址 ┌──────────────────────────────┐ │ struct pthread (TCB) │ ← 线程描述表pthread_t 就是这个结构体的首地址 ├──────────────────────────────┤ │ TLS │ ← 线程局部存储: 存储__thread 变量的副本 ├──────────────────────────────┤ │ │ │ Thread stack │ ← 线程栈 向下增长 │ (default 8MB) │ └──────────────────────────────┘ Low addresses 低地址A.struct pthread-TCBstruct pthread { /* ---- 标识信息 ---- */ struct pthread *self; // ★指向自身用于 %fs:0 快速定位 pid_t tid; // ★内核分配的 LWP IDgettid 的值 pid_t pid; // ★进程 IDtgid /* ---- 线程栈信息 ---- */ 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. 线程局部存储 -TLSTLS解决的问题全局变量和静态变量在所有线程间共享但是很多场景需要看起来像全局变量实际上每个线程各有一份的数据。例如errno、日志缓冲区、线程私有缓存等。关键字__thread和thread_local// C 语言GCC 扩展 __thread int counter 0; //每个线程都能看见但是y独立的一份 // C11 标准 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. 回收线程栈和 TCBmmap 释放) │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::functionvoid(); // 内部上下文: 承载可调用对象 线程名 // 独立分配在堆上, 是为了把生命周期与 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 局部变量 // C11 下 inline 函数的 static 局部量跨编译单元共享, 头文件安全 inline int NextThreadId() { static int id 1; return id; } class Thread { public: // 接受任意可调用对象 参数, 用 std::bind 绑参后转为 std::functionvoid() 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::forwardF(f), std::forwardArgs(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_castThreadData *(arg); td-_func(); // 执行用户回调 delete td; // 跳板作为最终消费者, 释放上下文 return nullptr; } private: pthread_t _tid; std::string _name; // 自己保留一份, name() 在 join/detach 后仍可用 ThreadStatus _status; ThreadData *_td nullptr; }; } // namespace ThreadModule