第 30 章 线程:线程同步 (Threads: Thread Synchronization)
核心结论
-
mutex 提供对共享资源的互斥访问:locked/unlocked 两态;lock unlock 必须成对;同一线程重复 lock 默认死锁(Linux NORMAL mutex);unlock 未持有的 mutex 结果未定义;性能优——无竞争时纯用户态 futex(无系统调用)。
-
mutex 类型:NORMAL / ERRORCHECK / RECURSIVE / DEFAULT:NORMAL 不检查错误(Linux 默认);ERRORCHECK 返回错误码(调试用);RECURSIVE 维护 lock count(同线程可多次 lock);DEFAULT SUSv3 未定义(Linux 等同 NORMAL)。
-
死锁与避免:多个 mutex 锁顺序不一致是经典死锁源;解决方案——mutex 层次(按固定顺序 lock)或 try-and-backoff(trylock + 失败全释放)。
-
条件变量用于「状态变化通知」:必须与 mutex 配对使用;wait 步骤原子地 unlock mutex + 阻塞 + 被 signal 后 relock;signal/broadcast 区别——signal 至少唤醒一个,broadcast 唤醒全部。
-
pthread_cond_wait 必须放在 while 循环内而非 if:原因——其他线程先 wake、loose predicate 设计、spurious wake-ups(SUSv3 允许);signal 可在 unlock 之前或之后(unlock 后 signal 可能性能更优——wait morphing)。
-
线程池/join any 模式:pthread_join 只针对特定 TID(无 join any);用 mutex + cond 模拟——线程终止时 signal 条件变量,主线程 while loop 检查 + 扫描所有 TID + pthread_join。
|
本章主旨
本章深入 POSIX 线程同步的两大基石——mutex 与条件变量。读者应掌握:mutex 静态/动态初始化、lock/unlock/trylock/timedlock、4 种 mutex 类型、死锁避免;条件变量 signal/broadcast/wait/timedwait、为何必须用 while 而非 if、condition variable + mutex 模拟 join any(thread_multijoin 模式)。理解「condition variable 无状态,只是一个通知机制」是避免常见错误的关键——signal 在没有 waiter 时会丢失。这是写线程池、生产者-消费者、读写锁(用 mutex+cond 实现)等并发模式的基础。 |
一、核心概念
本章围绕 6 个核心概念展开:从 mutex 基础入手,到 mutex 类型与死锁、动态初始化、条件变量机制、wait 循环模式,最后到 join any 模式。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
mutex 基础 |
pthread_mutex_t 类型;两态(locked/unlocked);locked 时其他线程 lock 阻塞;owner-only unlock;静态 |
§30.1;静态初始化仅默认属性;动态用于堆/栈 mutex 或自定义属性;图 30-2 展示临界区保护;mutex 操作必须作用于原始 mutex(非副本)。 |
mutex 性能与 futex |
无竞争时 mutex lock/unlock 用原子机器指令(无系统调用);有竞争时通过 futex 系统调用进入内核;NPTL mutex 实现基于 futex;锁/解锁 < 10x 自增操作成本。 |
§30.1.3;fcntl 锁 44s、System V semaphore 28s(20M 次)对比 mutex 几秒;futex 是「fast user space mutex」;详见 [Drepper, 2004(a)]。 |
mutex 类型与错误检查 |
|
§30.1.7;Listing 30-3 演示 settype;Linux 非标静态初始化 |
死锁与 mutex 层次 |
多线程多 mutex 时按不一致顺序 lock → 死锁;解决——mutex 层次(所有线程按相同顺序 lock);try-and-backoff(trylock + 失败全释放 + 重试)——效率低但灵活。 |
§30.1.4;图 30-3;典型场景——A lock mtx1 等 mtx2,B lock mtx2 等 mtx1;always lock mtx1 first 即可避免。 |
条件变量与 mutex 配对 |
pthread_cond_t 类型;必须与 mutex 配对(所有并发 waiter 用同一 mutex);wait 步骤原子地 unlock + block + relock;signal/broadcast——signal 至少唤醒一个、broadcast 全部;signal 在 unlock 之后调用(wait morphing 优化)。 |
§30.2.2;PTHREAD_COND_INITIALIZER 静态初始化;pthread_cond_init 动态;wait 必须 while 循环包裹。 |
join any 模式(条件变量模拟) |
pthread_join 只针对特定 TID;用 mutex + cond 模拟「join 任意已终止线程」——线程终止时 state=TS_TERMINATED + signal threadDied;主线程 while numLive>0:wait + 扫描所有 TID + join 已终止的。 |
§30.2.4;Listing 30-4 thread_multijoin.c;状态机 TS_ALIVE → TS_TERMINATED → TS_JOINED;numLive/numUnjoined 计数;thread array 跟踪。 |
二、详细笔记
30.1 mutex 基础
What:pthread_mutex_t 类型 mutex;两态 locked/unlocked;lock/unlock 操作;静态或动态初始化;advisory 同步机制。
Why:防止多线程同时修改共享变量;critical section 的原子性保证;现代 NPTL mutex 用 futex 实现,无竞争时无系统调用。
How:
mutex 三态语义(§30.1):
-
unlocked → 任何线程 lock 立即成功。
-
locked by thread A → 其他线程 lock 阻塞直到 unlock。
-
同一线程重复 lock 默认死锁(Linux NORMAL mutex)。
-
unlock 不是 owner 的 mutex 结果未定义。
-
unlock 未 locked 的 mutex 结果未定义。
API(§30.1.1-§30.1.2):
// 摘自《The Linux Programming Interface》第 30 章
#include <pthread.h>
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化 */
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); /* 失败 EBUSY */
int pthread_mutex_timedlock(pthread_mutex_t *mutex,
const struct timespec *abstime); /* 超时 ETIMEDOUT */
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
要点:
-
PTHREAD_MUTEX_INITIALIZER仅用于静态 + 默认属性。 -
mutex 操作必须作用于原始 mutex——SUSv3 规定 mutex 副本操作结果未定义。
-
每线程用 mutex 协议访问共享资源:lock → 访问 → unlock(critical section)。
-
多线程 try-lock 可能 starvation——>lock 阻塞队列中线程持续获取锁(pseudocode 演示)。
-
mutex 是 advisory——线程可自由忽略,但正确同步要求所有线程配合。
性能(§30.1.3):
-
无竞争 lock/unlock 用原子机器指令——无系统调用。
-
有竞争时通过 futex 系统调用进入内核。
-
对比:fcntl 锁 20M 次 = 44s;SysV 信号量 28s;mutex 几秒(同一 x86-32 Linux 2.6.31)。
-
futex = Fast Userspace muTEX;详见 [Drepper, 2004(a)] 与 [Franke et al., 2002]。
When:保护所有共享可变状态——全局变量、堆对象、共享内存区;持锁时间尽可能短;lock 顺序需明确避免死锁。
Example:
// 摘自《The Linux Programming Interface》第 30 章 — Listing 30-2
// 摘自 threads/thread_incr_mutex.c
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *threadFunc(void *arg) {
int loops = *((int *) arg);
int loc, j, s;
for (j = 0; j < loops; j++) {
s = pthread_mutex_lock(&mtx);
if (s != 0) errExitEN(s, "pthread_mutex_lock");
loc = glob; loc++; glob = loc;
s = pthread_mutex_unlock(&mtx);
if (s != 0) errExitEN(s, "pthread_mutex_unlock");
}
return NULL;
}
30.1.7 mutex 类型
What:4 种 mutex 类型——NORMAL/ERRORCHECK/RECURSIVE/DEFAULT;决定错误检查策略和递归 lock 行为。
Why:选择 mutex 类型平衡「性能」与「调试友好度」;递归锁用于需要递归函数的场景;错误检查 mutex 用于开发期发现 bug。
How:
类型对比(§30.1.7):
| 类型 | 重复 lock 行为 | unlock 错误行为 |
|---|---|---|
|
死锁(默认) |
未定义(Linux 上 unlock 成功) |
|
返回 EDEADLK |
返回 EPERM |
|
计数 +1 |
返回 EPERM |
|
未定义(SUSv3) |
未定义(Linux 等同 NORMAL) |
设置类型(§30.1.7):
// 摘自《The Linux Programming Interface》第 30 章 — Listing 30-3
pthread_mutex_t mtx;
pthread_mutexattr_t mtxAttr;
int s;
s = pthread_mutexattr_init(&mtxAttr);
s = pthread_mutexattr_settype(&mtxAttr, PTHREAD_MUTEX_ERRORCHECK);
s = pthread_mutex_init(&mtx, &mtxAttr);
s = pthread_mutexattr_destroy(&mtxAttr);
RECURSIVE mutex 语义(§30.1.7):
-
首次 lock:count = 1;线程成为 owner。
-
同线程再次 lock:count++。
-
同线程 unlock:count--;count = 0 时释放。
-
其他线程 unlock:返回 EPERM。
-
适用场景:递归函数、回调中需 lock 同 mutex 的库。
Linux 非标静态初始化(§30.1.7):
-
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP等——可移植代码避免使用。
When:默认用 NORMAL(无开销);开发期用 ERRORCHECK(找 bug);递归函数/库需要 RECURSIVE;PTHREAD_MUTEX_DEFAULT 是 PTHREAD_MUTEX_INITIALIZER 默认值。
Example:
// 摘自《The Linux Programming Interface》第 30 章 — 递归 mutex 使用
pthread_mutex_t mtx = PTHREAD_RECURSIVE_MUTEX_INITIALIZER; /* Linux 非标 */
void recursive_helper(int depth) {
pthread_mutex_lock(&mtx);
/* 访问共享资源 */
if (depth > 0) recursive_helper(depth - 1);
pthread_mutex_unlock(&mtx);
}
30.1.4 死锁
What:两个或多个线程互相等待对方持有的锁——永久阻塞。
Why:理解死锁成因是设计多 mutex 程序的关键;mutex 层次是最简单可靠的解决方案。
How:
经典死锁场景(§30.1.4,图 30-3):
-
Thread A: lock(mutex1) → lock(mutex2) blocks。
-
Thread B: lock(mutex2) → lock(mutex1) blocks。
mutex 层次方案:
-
强制所有线程按相同顺序 lock 多个 mutex——例如总是先 mutex1 后 mutex2。
-
即使无逻辑层次,也可指定任意层次(如按 mutex 地址排序)。
-
简单、可靠、零开销——推荐作为首选。
try-and-backoff 方案:
-
第一个 mutex 用 lock;后续用 trylock。
-
任一 trylock 失败 → 释放所有 mutex → 重试(可能 sleep 一段)。
-
优点:无需固定层次。
-
缺点:可能多次重试;starvation 风险;不高效。
-
见 [Butenhof, 1996]。
When:写多 mutex 代码时——确定 mutex 层次;lock 顺序不一致时考虑重构;不要在持锁时调用外部函数(可能间接 lock)。
Example(mutex 层次):
// 摘自《The Linux Programming Interface》第 30 章
/* 全局约定:先 lock mtx_a 再 lock mtx_b */
void func_a(void) {
pthread_mutex_lock(&mtx_a);
pthread_mutex_lock(&mtx_b);
/* ... */
pthread_mutex_unlock(&mtx_b);
pthread_mutex_unlock(&mtx_a);
}
void func_b(void) {
/* 必须按相同顺序 */
pthread_mutex_lock(&mtx_a);
pthread_mutex_lock(&mtx_b);
/* ... */
pthread_mutex_unlock(&mtx_b);
pthread_mutex_unlock(&mtx_a);
}
30.1.5 动态 mutex 初始化
What:pthread_mutex_init/destroy 用于动态/栈 mutex 或自定义属性 mutex。
Why:某些场景不能用静态初始化——堆上 mutex、栈 mutex、非默认属性 mutex。
How:
需要动态初始化的场景(§30.1.5):
-
mutex 动态分配在堆上(如链表节点内嵌 mutex)。
-
mutex 是栈上的自动变量。
-
静态 mutex 但用非默认属性(如 ERRORCHECK 类型)。
销毁(§30.1.5):
-
pthread_mutex_destroy释放 mutex 资源;不必 destroy 静态初始化 mutex。 -
必须 unlocked 时 destroy;之后不能 lock。
-
动态内存中的 mutex:free 内存前先 destroy。
-
栈 mutex:宿主函数返回前 destroy。
-
destroy 后可再次 init 复用。
When:嵌入到动态分配结构中的 mutex(如 hash 表每个 bucket 一 mutex);需要错误检查时用动态 init + settype。
Example:
// 摘自《The Linux Programming Interface》第 30 章 — 堆中 mutex
struct node {
int value;
pthread_mutex_t mtx;
struct node *next;
};
struct node *n = malloc(sizeof(*n));
if (n == NULL) abort();
pthread_mutex_init(&n->mtx, NULL); /* 默认属性 */
/* ... 使用 n->mtx ... */
pthread_mutex_destroy(&n->mtx);
free(n);
30.2 条件变量
What:pthread_cond_t 类型;用于「状态变化通知」;与 mutex 配对使用;signal/broadcast 通知,wait 阻塞直到通知。
Why:解决「轮询浪费 CPU」问题——mutex 保护共享变量,cond 通知状态变化;生产者-消费者、join any 都基于此。
How:
API(§30.2.2):
// 摘自《The Linux Programming Interface》第 30 章
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_signal(pthread_cond_t *cond); /* 至少唤醒一个 waiter */
int pthread_cond_broadcast(pthread_cond_t *cond); /* 唤醒全部 waiter */
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
const struct timespec *abstime);
pthread_cond_wait 步骤(§30.2.2):
-
unlock mutex(释放以便其他线程修改共享变量)。
-
阻塞调用线程直到 cond 被 signal。
-
被 wake 后 relock mutex。
-
步骤 1-2 原子——其他线程不可能在 unlock 后、block 前 signal。
signal vs broadcast(§30.2.2):
-
signal:至少唤醒一个 waiter;若多个等待,唤醒哪个未定义。
-
broadcast:唤醒所有 waiter;处理 spurious/redundant wake-up 后回到 wait。
-
只在「所有等待线程执行相同任务」时用 signal;否则用 broadcast。
-
signal 可能引起「thundering herd」——所有等待线程被唤醒但只有一个能继续。
signal 与 unlock 顺序(§30.2.2):
-
SUSv3 允许两种顺序:先 unlock 后 signal / 先 signal 后 unlock。
-
推荐 unlock 后 signal——某些实现用 wait morphing 避免多余 context switch。
-
若先 signal 再 unlock:waiter 立即 wake,但 mutex 仍 locked → 立即重新 sleep → 多余 context switch。
condition variable 性质(§30.2.2):
-
无状态——纯通知机制。
-
signal 时无 waiter → signal 丢失。
-
新 waiter 不会被旧 signal 唤醒——只 wake 后续 signal。
When:生产者-消费者模式、线程池任务派发、join any 模拟、读写等待、状态机同步。
Example(生产者-消费者):
// 摘自《The Linux Programming Interface》第 30 章 — prod_condvar.c 简化
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0;
/* 生产者 */
pthread_mutex_lock(&mtx);
avail++;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond); /* 推荐 unlock 后 signal */
/* 消费者 */
pthread_mutex_lock(&mtx);
while (avail == 0) /* 必须 while 而非 if */
pthread_cond_wait(&cond, &mtx);
/* 现在 avail > 0,处理 */
avail--;
pthread_mutex_unlock(&mtx);
30.2.3 为何 pthread_cond_wait 必须在 while 循环内
What:wait 返回后必须重新检查 predicate(共享变量状态)——可能不满足预期;必须用 while 而非 if。
Why:SUSv3 允许 wait 虚假 wake-up + signal 设计为「loose predicate」;这是 Pthreads 编程的最常见陷阱之一。
How:
必须 while 而非 if 的原因(§30.2.3):
-
其他线程可能先 wake:mutex 关联的 cond 可能有多个 waiter;即使 signal 线程设置 predicate 到期望状态,其他 waiter 之一可能先获取 mutex 并改变 predicate。
-
Loose predicate 设计:signal 可设计为「可能有事可做」而非「有事可做」——waiter 必须自己验证。
-
Spurious wake-up:SUSv3 允许某些实现下 wait 在无 signal 时也返回——多处理器系统的实现细节。
while 模式(§30.2.3):
// 摘自《The Linux Programming Interface》第 30 章
pthread_mutex_lock(&mtx);
while (predicate_not_satisfied)
pthread_cond_wait(&cond, &mtx);
/* 此时 predicate 满足 */
process_shared_resource();
pthread_mutex_unlock(&mtx);
When:任何 pthread_cond_wait 都必须包在 while 循环内;这是硬性规则,没有例外。
Example:
// 摘自《The Linux Programming Interface》第 30 章 — 错误 vs 正确
/* 错误:spurious wake-up 可能破坏逻辑 */
if (avail == 0)
pthread_cond_wait(&cond, &mtx); /* 醒来后不检查 avail */
consume(avail); /* avail 可能仍为 0 */
/* 正确:while 循环重新检查 */
while (avail == 0)
pthread_cond_wait(&cond, &mtx);
consume(avail);
30.2.4 join any 模式(thread_multijoin)
What:用 mutex + cond 模拟「pthread_join any」——线程池回收任一终止线程;维护全局 thread array + 状态机 + 计数器。
Why:pthread_join 只针对特定 TID——worker pool 需要回收任一完成线程;条件变量 + mutex 是经典解决方案。
How:
状态机(§30.2.4,Listing 30-4):
| 状态 | 含义 |
|---|---|
|
线程在运行 |
|
线程已终止但未 join |
|
线程已 join(state 仅作历史记录) |
全局变量:
-
totThreads:创建线程总数。 -
numLive:存活 + 已终止未 join 的线程数。 -
numUnjoined:已终止但未 join 的线程数。 -
thread[]:每线程记录 tid + state + sleepTime。
线程终止时(§30.2.4):
-
sleep(work_time)。
-
pthread_mutex_lock(&threadMutex)。
-
numUnjoined++;thread[idx].state = TS_TERMINATED。
-
pthread_mutex_unlock(&threadMutex)。
-
pthread_cond_signal(&threadDied)。
-
return NULL。
主线程回收循环(§30.2.4):
// 摘自《The Linux Programming Interface》第 30 章 — Listing 30-4 简化
// 摘自 threads/thread_multijoin.c
while (numLive > 0) {
pthread_mutex_lock(&threadMutex);
while (numUnjoined == 0)
pthread_cond_wait(&threadDied, &threadMutex);
for (idx = 0; idx < totThreads; idx++) {
if (thread[idx].state == TS_TERMINATED) {
pthread_join(thread[idx].tid, NULL);
thread[idx].state = TS_JOINED;
numLive--;
numUnjoined--;
printf("Reaped thread %d (numLive=%d)\n", idx, numLive);
}
}
pthread_mutex_unlock(&threadMutex);
}
要点:
-
wait 在 while numUnjoined==0 内——确保有线程可 join 时才退出 wait。
-
pthread_join 在 mutex 持锁时调用——Pthread 函数允许(不违反 mutex 规则)。
-
扫描整个 array——确保 join 所有已 terminate 的线程。
-
numLive==0 时退出——所有线程回收完毕。
When:线程池实现、worker 模式、批量创建并等待所有线程完成。
Example:见上述代码。
三、关键图表
|
mutex 类型行为对照表
性能:NORMAL > RECURSIVE > ERRORCHECK(开销递增)。 |
|
条件变量 wait 步骤详解
|
四、思维导图
mindmap
root((第 30 章 线程同步))
mutex 基础
pthread mutex t
locked unlocked 两态
owner only unlock
advisory 同步
临界区保护
MUTEX INITIALIZER
mutex 性能
无竞争 原子指令
有竞争 futex 系统调用
fast userspace mutex
比 fcntl sem 快 10x
mutex 类型
NORMAL 默认 Linux
ERRORCHECK 调试用
RECURSIVE 递归锁
DEFAULT SUSv3 未定义
settype pthread mutexattr t
死锁
多 mutex 不一致顺序
mutex 层次 首选
try and backoff 备选
trylock 失败全释放重试
动态 mutex
堆上 mutex
栈 mutex
非默认属性
destroy unlocked
destroy 后可 reinit
条件变量
pthread cond t
必须配 mutex
signal 唤醒一个
broadcast 唤醒全部
wait unlock block relock
signal 丢失 无状态
推荐 unlock 后 signal
wait while 循环
其他线程先 wake
loose predicate
spurious wake up
SUSv3 允许
硬性规则无例外
join any 模式
thread_multijoin
TS ALIVE TERMINATED JOINED
numLive numUnjoined
wait threadDied
扫描 array 回收
线程池基础
五、重点与易错点
-
mutex 是 advisory(非强制)——线程可自由忽略;但正确同步要求所有线程配合。
-
mutex 操作必须作用于原始 mutex——SUSv3 规定 mutex 副本操作结果未定义;不能 memcpy mutex。
-
每线程用 mutex 协议访问共享资源:lock → 访问 → unlock;持锁时间尽可能短(避免阻塞其他线程)。
-
静态初始化 PTHREAD_MUTEX_INITIALIZER 仅默认属性——动态分配 mutex 或非默认属性需 pthread_mutex_init。
-
mutex 类型决定错误检查行为:NORMAL 重复 lock 死锁;RECURSIVE 维护 lock count(递归函数/库);ERRORCHECK 调试期发现 bug。
-
死锁简单方案:mutex 层次——所有线程按相同顺序 lock 多个 mutex;推荐作为首选策略。
-
try-and-backoff 适用于无固定层次场景——trylock + 失败全释放 + 重试;代价是效率低。
-
Linux mutex 基于 futex——无竞争无系统调用;有竞争才用 futex();性能远优于 fcntl 锁和 SysV 信号量。
-
条件变量必须与 mutex 配对——所有并发 waiter 用同一 mutex;SUSv3 规定「cond + 多 mutex」结果未定义。
-
pthread_cond_wait 原子地 unlock + block + relock——避免「unlock 后但未 block」时其他线程 signal;这是「cond 必须配 mutex」的根因。
-
pthread_cond_wait 必须包在 while 循环内——不是 if;spurious wake-up + 其他线程先 wake + loose predicate 都要求重新检查;这是 Pthreads 编程硬性规则。
-
pthread_cond_signal vs broadcast:signal 唤醒至少一个(可能 thundering herd);broadcast 唤醒全部;执行相同任务用 signal,不同任务用 broadcast。
-
signal 无 waiter 时丢失——cond 无状态,纯通知机制;不能「提前 signal 留待后续 wait」。
-
推荐 unlock 后 signal——某些实现的 wait morphing 优化避免多余 context switch。
-
signal vs unlock 顺序 SUSv3 都允许——但 unlock+signal 是更优实践。
-
join any 模式 = mutex + cond + 状态机 + 计数器——是线程池回收 worker 的经典模式;pthread_join 无「join any」必须自实现。
-
pthread_join 可在 mutex 持锁时调用——Pthreads 函数不违反 mutex 规则;只有「持锁时调用可能 lock 同 mutex 的函数」才会死锁。
-
pthread_cond_timedwait 用绝对时间 abstime——非相对时间;需用 clock_gettime(CLOCK_REALTIME) 计算绝对 deadline;返回值 ETIMEDOUT。
-
pthread_mutex_timedlock 同样用绝对时间——超时返回 ETIMEDOUT;用得少——持锁时间应短。
-
pthread_mutex_trylock 失败 EBUSY——轮询用 trylock 可能 starvation;推荐设计「锁等待而非轮询」。
-
跨章衔接:第 29 章线程基础 → 第 30 章 mutex/cond 同步 → 第 31 章 thread-safety(POSIX 函数可重入性)+ thread-specific data(per-thread 存储);第 32 章线程取消 → cleanup handler 与 cond/cancel interaction;第 33 章线程细节 → 信号、LinuxThreads vs NPTL、pthread_atfork。
-