第 29 章 线程:介绍 (Threads: Introduction)

      +

      核心结论

      • 线程共享进程的全局内存(text/data/bss/heap)+ 大部分进程属性:每个线程有独立栈、线程 ID、信号屏蔽字、errno、线程特定数据、浮点环境等;线程栈都在同一虚拟地址空间内,可能交错。

      • 线程 vs 进程的核心权衡:线程创建更快(~10x)、共享数据只需访问全局变量;但所有线程共享地址空间——单线程 bug 可损坏所有线程;需 thread-safe 编程;多线程程序都跑同一程序。

      • pthread_create() 创建新线程:新线程从 start(arg) 开始执行;pthread_t buffer 在返回前不保证初始化;返回值用 errExitEN() 处理(pthread 函数返回 0/正错误码,非 -1/errno)。

      • pthread_exit()/return/cancel/exit 终止线程:主线程 return 或任何线程 exit → 整个进程终止;pthread_exit 只终止当前线程;retval 不能指向线程栈(线程终止后栈可能被新线程复用)。

      • pthread_join() 等价于 waitpid() 但只针对特定 TID:无「等任意线程」、无 WNOHANG;线程是 peer 关系——任何线程可 join 任何线程;join 后线程状态被回收(不 join 未 detach 线程 → 线程僵尸,浪费资源)。

      • pthread_detach() 或 pthread_attr_setdetachstate 标记线程自动清理:detach 后不能再 join;用于不关心返回值的 worker 线程。

      本章主旨

      本章是「线程」四章的导论——理解 POSIX 线程的基本模型与生命周期。读者应掌握:线程与进程的差异(共享 vs 私有属性)、Pthreads 数据类型(pthread_t 等不透明类型)、线程创建(pthread_create + start 函数)、线程终止(4 种方式)、线程 ID(pthread_self/pthread_equal)、join/detach 模型、线程属性(pthread_attr_t 含 detachstate/栈/调度)、线程 vs 进程的设计取舍。理解 pthread_join 的「只针对特定 TID」设计是写「线程池 + 工作队列」的关键;理解共享与私有属性的清晰划分是写正确同步代码的前提。

      一、核心概念

      本章围绕 6 个核心概念展开:从线程模型入手,到 Pthreads API 背景、线程创建、线程终止、线程 ID 与 join/detach,最后是线程 vs 进程的设计取舍。

      概念 定义 + 重要性 实现提示

      线程共享/私有属性

      线程共享:PID/PPID/PGID/SID/凭证/fd/信号 disposition/锁/cwd/root/umask/timers/资源限制/CPU 时间/nice。线程私有:TID/信号屏蔽字/线程特定数据/alternate signal stack/errno/浮点环境/调度策略/CPU 亲和性/capabilities/栈。

      §29.1;理解「共享什么」决定同步需求,「私有什么」决定 thread-local 状态;图 29-1 展示四线程进程内存布局。

      Pthreads 数据类型与 errno 模型

      pthread_t/mutex/cond/key/attr 是不透明类型——不能用 == 比较;每个线程有独立 errno(用宏实现为函数调用返回 modifiable lvalue);Pthreads 函数返回 0 或正错误码(非 -1/errno)。

      §29.2;编译加 -pthread(定义 _REENTRANT + 链接 libpthread);用 errExitEN(s, "…​") 包装错误检查。

      pthread_create()

      创建新线程,从 start(arg) 开始执行;pthread_t buffer 不保证在 pthread_create 返回前初始化——子线程若需自己的 ID 用 pthread_self();attr=NULL 用默认属性;arg 不能是线程栈上对象。

      §29.3;start 返回值不能与 PTHREAD_CANCELED 同值(线程取消时);retval 不能指向线程栈。

      线程终止 4 种方式

      start return / pthread_exit(retval) / pthread_cancel / exit()(或 main return)——后者终止所有线程;retval 不能在线程栈上(线程终止后栈可能被新线程复用)。

      §29.4;主线程 pthread_exit 不终止进程(区别于 main return);非 detach 线程必须 join 否则成为「线程僵尸」。

      pthread_join() / pthread_detach()

      join 类似 waitpid:等特定 TID 终止 + 回收状态;无「等任意」、无 WNOHANG(条件变量可模拟);detach 标记线程自动清理——不能再 join;不 detach 不 join → 线程僵尸。

      §29.5-§29.7;线程是 peer 关系——任何线程可 join 任何线程(包括「祖父线程 join 孙线程」);detach 控制终止后行为,不控制终止方式。

      线程 vs 进程的设计取舍

      线程优势:数据共享简单、创建快 ~10x、context switch 可能更快;线程劣势:单线程 bug 损所有线程、需 thread-safe、所有线程跑同一程序、虚拟地址空间共享。

      §29.9;多进程 + 共享内存(mmap/SysV/POSIX)vs 多线程;信号在多线程中难处理(§33.2);一般原则:CPU bound + 数据共享 → 线程;隔离 + 不同程序 → 进程。

      二、详细笔记

      29.1 线程模型与共享/私有属性

      What:线程是进程内的执行流;同一进程的所有线程共享 text/data/bss/heap + 大部分进程属性;每个线程有独立栈、线程 ID、errno 等。

      Why:理解「什么共享、什么私有」是写多线程程序的根——共享决定同步需求,私有决定线程本地状态。

      How

      共享属性(§29.1):

      • 进程 ID、PPID、PGID、SID、控制终端。

      • 凭证:real/effective/saved UID/GID、supplementary GIDs。

      • 打开文件描述符表(独立 fd table 但共享打开文件描述 + 偏移 + 状态)。

      • fcntl record locks。

      • 信号 disposition。

      • 文件系统:umask/cwd/root。

      • 计时器:interval timer(setitimer)、POSIX timer(timer_create)、SysV semaphore undo。

      • 资源限制、CPU 时间(times)、资源使用(getrusage)、nice 值。

      私有属性(§29.1):

      • 线程 ID(pthread_self)、进程内的 TID 唯一性。

      • 信号屏蔽字(sigprocmask)。

      • 线程特定数据(pthread_key_create,详见第 31 章)。

      • 备用信号栈(sigaltstack)。

      • errno(线程特定宏实现)。

      • 浮点环境(fenv)。

      • 实时调度策略与优先级(SCHED_FIFO/RR/DEADLINE)。

      • CPU 亲和性(sched_setaffinity)。

      • capabilities(per-thread)。

      • 栈(局部变量 + 函数调用链)。

      线程栈的关键陷阱(§29.1):所有线程栈在同一虚拟地址空间——可能交错;栈帧随函数返回被复用;线程终止后栈可能被新线程复用——这是「retval 不能指向线程栈」的原因。

      When:设计多线程程序时——明确「共享数据需 mutex/cond」「私有数据用 pthread_key_create 或 thread-local 变量」。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — 共享 vs 私有
      /* 共享:全局变量 */
      int shared_counter = 0;
      
      /* 私有:线程特定 errno */
      errno = EINTR;   /* 只影响当前线程 */
      
      /* 私有:线程栈上的局部变量 */
      void *worker(void *arg) {
          int local = 42;          /* 其他线程看不到 */
          return NULL;
      }

      29.2 Pthreads API 背景

      What:POSIX.1c (1995) 标准化的 POSIX 线程 API——SUSv3 包含;定义一组不透明数据类型;errno 改为线程特定;Pthreads 函数返回 0 或正错误码。

      Why:理解 Pthreads API 的「反传统 UNIX」约定(不返回 -1/errno)是写正确 Pthreads 代码的前提;编译选项 -pthread 是必须的。

      How

      不透明数据类型(§29.2,表 29-1):

      • pthread_t:线程 ID(Linux NPTL 是 unsigned long,实际是 cast 的指针)。

      • pthread_mutex_t / pthread_mutexattr_t:互斥量与属性。

      • pthread_cond_t / pthread_condattr_t:条件变量与属性。

      • pthread_key_t:线程特定数据 key。

      • pthread_once_t:一次性初始化控制。

      • pthread_attr_t:线程属性对象。

      关键约束(§29.2):

      • 不能用 == 比较 pthread_t——必须用 pthread_equal(t1, t2)

      • 不能用 printf("%ld", (long) tid) 打印——非可移植;SUSv3 允许 pthread_t 是结构体。

      • errno 改为线程特定——<errno.h> 必须包含;extern int errno 在 SUSv3 中不允许。

      Pthreads 函数返回约定(§29.2):

      • 0 → 成功。

      • 正值 → 错误码(与 errno 值同集合,但直接返回,不通过 errno)。

      • 示例:s = pthread_create(…​); if (s != 0) errExitEN(s, "pthread_create");

      编译选项(§29.2):

      • Linux:cc -pthread(定义 _REENTRANT + 链接 libpthread)。

      • Solaris/HP-UX:cc -mt

      • Tru64:cc -pthread(同 Linux)。

      When:任何使用 Pthreads 的程序必须包含 <pthread.h>、编译加 -pthread;用 errExitEN 包装所有 pthread_* 调用。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — Pthreads 错误处理模式
      #include <pthread.h>
      int s;
      pthread_t t;
      s = pthread_create(&t, NULL, func, &arg);
      if (s != 0) errExitEN(s, "pthread_create");
      /* 不能再用 errno 检查 — 错误码在 s */

      29.3 pthread_create() 创建线程

      Whatpthread_create(&t, attr, start, arg) 创建新线程;新线程从 start(arg) 开始执行;调用者继续下一语句。

      Why:多线程程序入口;理解 start/arg 语义(单一指针参数,复合参数需用结构体)是写线程函数的关键。

      How

      签名(§29.3):

      // 摘自《The Linux Programming Interface》第 29 章
      #include <pthread.h>
      int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                         void *(*start)(void *), void *arg);

      要点(§29.3):

      • start 函数签名 void ()(void *);arg 是 void *——可传递任意类型指针;多参数用结构体指针。

      • pthread_t buffer 在 pthread_create 返回前不一定初始化——新线程可能在 pthread_create 返回前就开始运行;新线程若需自己的 ID 用 pthread_self()

      • attr=NULL → 默认属性(joinable + 默认栈 + 默认调度)。

      • start 返回值是 void *——同样可传递任意指针;join 时通过 pthread_join 的 retval 接收。

      • 不要让 start 返回与 PTHREAD_CANCELED 相同的整数(cast 后的)——join 时会误判为被取消。

      • pthread_create 返回后无线程调度保证——多线程可能被同时调度到不同 CPU(多核系统);需同步见第 30 章。

      When:任何需要并发执行的场景——网络服务器 worker、并行计算、异步 I/O 处理器;优先考虑用 pthread_attr_setdetachstate 创建时直接 detach 而非 join。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — Listing 29-1
      // 摘自 threads/simple_thread.c
      static void *threadFunc(void *arg) {
          char *s = (char *) arg;
          printf("%s", s);
          return (void *) strlen(s);
      }
      int main(int argc, char *argv[]) {
          pthread_t t1;
          void *res;
          int s = pthread_create(&t1, NULL, threadFunc, "Hello world\n");
          if (s != 0) errExitEN(s, "pthread_create");
          printf("Message from main()\n");
          s = pthread_join(t1, &res);
          if (s != 0) errExitEN(s, "pthread_join");
          printf("Thread returned %ld\n", (long) res);
          exit(EXIT_SUCCESS);
      }

      29.4 线程终止

      What:线程终止 4 种方式——start return / pthread_exit / pthread_cancel / exit()(或 main return);后两者终止整个进程。

      Why:理解线程终止语义是控制程序生命周期的关键——main return 终止所有线程 ≠ pthread_exit 仅终止主线程。

      How

      pthread_exit()(§29.4):

      // 摘自《The Linux Programming Interface》第 29 章
      #include <pthread.h>
      void pthread_exit(void *retval);

      关键约束:

      • retval 不能指向线程栈上的对象——线程终止后栈帧被新线程复用。

      • pthread_exit 等价于 start 函数 return,但可从任意被 start 调用的函数中调用。

      • 主线程调 pthread_exit → 进程继续运行直到所有线程终止或 exit。

      • 主线程调 exit() 或 main return → 进程立即终止,未结束线程被强制 kill。

      • pthread_cancel 在第 32 章详细讨论。

      When:线程函数执行完毕 → return;线程需提前退出但保留其他线程 → pthread_exit;线程退出时需清理资源 → 注册 pthread_cleanup_push handler(详见第 32 章)。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — 主线程退出模式
      void *worker(void *arg) {
          /* 长期运行的工作 */
          while (!done) {
              /* ... */
          }
          pthread_exit((void *) 0);    /* 不调用 exit() */
      }
      int main() {
          pthread_t t;
          pthread_create(&t, NULL, worker, NULL);
          sleep(5);
          done = 1;
          pthread_join(t, NULL);
          /* 主线程不调 exit,worker 自然终止 */
          return 0;
      }

      29.5-29.6 线程 ID 与 pthread_join()

      What:每个线程有 pthread_t ID(pthread_self 返回);pthread_join 等价 waitpid 但只等特定 TID——无「等任意」、无非阻塞。

      Why:线程是 peer 关系——任何线程可 join 任何线程;理解 join 模型的限制(不能等任意线程)是用条件变量实现线程池的前提。

      How

      线程 ID API(§29.5):

      // 摘自《The Linux Programming Interface》第 29 章
      pthread_t pthread_self(void);
      int pthread_equal(pthread_t t1, pthread_t t2);   /* 非 0 = 相等 */

      关键点:

      • pthread_t 是不透明——必须用 pthread_equal 比较。

      • Linux NPTL 中 pthread_t 是 cast 的指针——可移植代码不要解引用。

      • pthread_t ID 可被重用——join 后或 detach 线程终止后 ID 可能被新线程复用。

      • POSIX pthread_t ≠ Linux gettid() 系统调用返回值——前者是用户态,后者是内核线程 ID。

      pthread_join()(§29.6):

      // 摘自《The Linux Programming Interface》第 29 章
      #include <pthread.h>
      int pthread_join(pthread_t thread, void **retval);

      要点(§29.6):

      • 等特定 thread 终止;已终止则立即返回。

      • retval 非 NULL → 接收终止线程的返回值(return 或 pthread_exit 指定的)。

      • 无「等任意线程」——SUSv3 故意限制;否则库函数无法安全 join 私有线程(可能 join 错了 ID)。

      • 无 WNOHANG 非阻塞——用条件变量 + mutex 实现(§30.2.4)。

      • 线程是 peer——A 创建 B 创建 C,A 可 join C(区别于进程父子层级)。

      • 不可对已 join 的 TID 再次 join——结果未定义(可能错误 join 到重用 ID 的新线程)。

      • 不 join 未 detach 线程 → 线程僵尸;积累到上限后 pthread_create 失败 EAGAIN。

      When:关心线程返回值 → join;不关心返回值且不希望线程僵尸 → detach;线程池 + 工作者 → 用条件变量模拟「join any」。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — 祖父线程 join 孙线程
      pthread_t grandchild;
      void *grandchild_fn(void *arg) {
          return (void *) 42;
      }
      void *child_fn(void *arg) {
          pthread_t gc;
          pthread_create(&gc, NULL, grandchild_fn, NULL);
          pthread_exit(NULL);   /* child 退出,但孙线程继续 */
      }
      int main() {
          pthread_t c, gc;
          pthread_create(&c, NULL, child_fn, NULL);
          pthread_join(c, NULL);    /* 等 child */
          /* 祖父线程直接 join 孙线程 */
          pthread_join(gc, NULL);
          return 0;
      }

      29.7-29.8 detach 与线程属性

      What:pthread_detach 标记线程自动清理(不可再 join);pthread_attr_t 含 detachstate/栈地址大小/调度策略与优先级等属性。

      Why:用 detach 避免线程僵尸 + 减少 join 样板;线程属性用于在创建时配置栈大小、调度策略等。

      How

      pthread_detach()(§29.7):

      // 摘自《The Linux Programming Interface》第 29 章
      #include <pthread.h>
      int pthread_detach(pthread_t thread);

      要点:

      • detach 后线程终止时自动清理,不能再 join。

      • 线程可自 detach:pthread_detach(pthread_self())

      • detach 不影响线程运行——只是终止后的清理方式。

      • exit() / main return 仍终止所有线程(detach 也不免疫)。

      线程属性(§29.8):

      // 摘自《The Linux Programming Interface》第 29 章 — Listing 29-2
      // 摘自 threads/detached_attrib.c
      pthread_attr_t attr;
      int s = pthread_attr_init(&attr);
      s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
      pthread_t thr;
      s = pthread_create(&thr, &attr, threadFunc, (void *) 1);
      s = pthread_attr_destroy(&attr);   /* 销毁属性对象 */

      常用属性:

      • detachstatePTHREAD_CREATE_JOINABLE(默认)/ PTHREAD_CREATE_DETACHED

      • stackaddr + stacksize:自定义线程栈(默认栈大小通常 8 MB;可缩小节省虚拟地址空间)。

      • schedpolicy + schedparam:调度策略与优先级(SCHED_OTHER/FIFO/RR/DEADLINE)。

      • inheritschedPTHREAD_INHERIT_SCHED(继承创建者)/ PTHREAD_EXPLICIT_SCHED(用 attr)。

      • scope:争用范围(PTHREAD_SCOPE_SYSTEM/PTHREAD_SCOPE_PROCESS,Linux 仅前者)。

      • guardsize:栈溢出保护页大小(默认 PAGE_SIZE)。

      When:worker 线程用 detach 避免 join 样板;大量线程(数千)需定制栈大小;实时线程用 schedpolicy 设置 FIFO/RR。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — 创建时直接 detach
      // 摘自 threads/detached_attrib.c
      pthread_attr_t attr;
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
      pthread_t thr;
      pthread_create(&thr, &attr, threadFunc, (void *) 1);
      pthread_attr_destroy(&attr);
      /* 不能 pthread_join(thr, NULL) — 已 detach */

      29.9 线程 vs 进程

      What:设计多任务程序时需在「线程共享地址空间」与「进程独立地址空间」之间选择。

      Why:理解两种模型的取舍是系统架构设计的关键——CPU-bound 并行、I/O 密集、隔离需求等都影响选择。

      How

      线程优势(§29.9):

      • 数据共享简单——直接读写全局变量。

      • 创建快 ~10x——无需复制页表。

      • Context switch 可能更快——共享地址空间,TLB 命中率高。

      线程劣势(§29.9):

      • 需 thread-safe 编程——所有库函数都需考虑并发;详见第 31 章。

      • 单线程 bug 可损坏所有线程——空指针解引用 → 整个进程 crash。

      • 虚拟地址空间共享——大量线程受限于进程 VA 空间(典型 x86-32 3 GB)。

      • 所有线程跑同一程序——不能 fork 不同二进制。

      • 信号处理复杂——见第 33 章。

      • 共享某些信息(fd、cwd 等)可能是优点也可能是缺点——取决于应用。

      一般原则(§29.9):

      • 数据共享密集 + 同程序 → 线程。

      • 隔离 + 不同程序 + 安全 → 进程。

      • I/O 密集 + 数据共享 → 线程(但第 63 章的 epoll/io_uring 可能是更好选择)。

      • 高吞吐网络服务器 → 线程池或 epoll 单线程(看具体负载)。

      When:写新并发程序时——先评估「需要共享多少数据」「是否需要不同程序」「隔离要求」;CPU-bound + 数据共享 → 线程池;I/O-bound + 高并发 → 优先 epoll/io_uring。

      Example

      // 摘自《The Linux Programming Interface》第 29 章 — 线程 vs 进程选择
      /* 数据共享密集场景:线程 */
      typedef struct { int request_count; double total; } Stats;
      static Stats shared_stats;            /* 多线程直接读写 — 需 mutex */
      /* 不同程序/强隔离:进程 */
      pid_t pid = fork();
      if (pid == 0) {
          /* 子进程执行不同二进制 — exec() */
          execl("/bin/worker", "worker", (char *) NULL);
      }

      三、关键图表

      线程共享/私有属性对照表
      进程属性 共享 线程私有

      进程 ID、PPID、PGID、SID

      控制终端

      凭证(real/effective/saved UID/GID)

      补充组 ID

      打开文件描述符(fd table)

      ✓(独立 fd table 但共享打开文件)

      fcntl record locks

      信号 disposition

      文件系统信息(umask/cwd/root)

      interval timer (setitimer)

      POSIX timer (timer_create)

      SysV semaphore undo

      资源限制

      CPU 时间 (times)

      资源使用 (getrusage)

      nice 值

      线程 ID (pthread_t)

      信号屏蔽字

      线程特定数据 (pthread_key)

      备用信号栈 (sigaltstack)

      errno

      浮点环境 (fenv)

      实时调度策略与优先级

      CPU 亲和性

      capabilities

      ✓(per-thread)

      栈(局部变量 + 函数调用链)

      Pthreads 函数返回约定
      传统 UNIX 系统调用 Pthreads 函数

      返回 -1 表示错误

      返回 0 表示成功

      errno 设置错误码

      返回正值 = 错误码(同 errno 值集合)

      检查 errno 后处理

      直接检查返回值 s

      errExit("func") 包装

      errExitEN(s, "func") 包装

      四、思维导图

      mindmap
        root((第 29 章 线程介绍))
          线程模型
            共享 text data bss heap
            共享 fd 凭证 cwd 计时器
            私有栈 TID errno
            私有信号屏蔽字 sched
            私有 capabilities
          Pthreads API
            POSIX 1c 1995 标准
            pthread t 不透明
            pthread equal 比较
            errno 线程特定
            返回 0 或正错误码
            编译 -pthread
          pthread create
            start arg 启动
            attr 默认 joinable
            pthread t 不保证先初始化
            pthread self 取自己 ID
            start 返回不能匹配 CANCELED
          线程终止
            start return
            pthread exit
            pthread cancel
            exit 终止所有线程
            retval 不能在栈上
          pthread join
            等特定 TID
            无 join any
            无 WNOHANG
            线程是 peer
            不 join 未 detach 线程僵尸
          detach 与属性
            pthread detach
            pthread attr t
            detachstate joinable detached
            stackaddr stacksize
            schedpolicy schedparam
            自定义栈节省 VA
          线程 vs 进程
            数据共享简单
            创建快 10x
            thread safe 必需
            单 bug 损所有线程
            VA 空间共享
            多进程强隔离

      五、重点与易错点

      1. 线程共享进程的全局内存 + 大部分进程属性——共享:PID/PPID/PGID/SID/凭证/fd/信号 disposition/cwd/root/umask/计时器/资源限制/nice;私有:TID/信号屏蔽字/线程特定数据/errno/浮点环境/调度/CPU 亲和性/capabilities/栈。

      2. pthread_t 是不透明类型——不能用 == 比较,必须用 pthread_equal;Linux NPTL 中是 cast 指针,可移植代码不要解引用。

      3. errno 在多线程中是宏展开为函数调用——每个线程有独立 errno;<errno.h> 必须包含;extern int errno 在 SUSv3 中不允许。

      4. Pthreads 函数返回 0 或正错误码——不是 -1/errno;用 errExitEN(s, "func") 而非 errExit("func") 包装。

      5. 编译加 -pthread——同时定义 _REENTRANT + 链接 libpthread;其他平台用 -mt(Solaris/HP-UX)。

      6. pthread_create 返回前 pthread_t buffer 不一定初始化——新线程可能在 pthread_create 返回前就运行;新线程需自己的 ID 用 pthread_self()。

      7. 线程终止的 retval 不能指向线程栈——线程终止后栈帧可能被新线程复用;同样规则适用 start 函数的 return 值。

      8. pthread_exit 不等于 exit——主线程 pthread_exit 仅终止主线程,其他线程继续;主线程 return / exit 终止整个进程。

      9. pthread_join 无「等任意」、无 WNOHANG——只等特定 TID;非阻塞 join 用条件变量 + mutex 模拟(§30.2.4)。

      10. 线程是 peer 关系——任何线程可 join 任何线程;A 创建 B 创建 C,A 可 join C;区别于进程父子层级。

      11. 不 join 未 detach 线程 → 线程僵尸——积累到上限后 pthread_create 失败 EAGAIN;同进程僵尸。

      12. detach 不影响线程运行——只控制终止后的清理方式;exit() / main return 仍强制终止所有线程(detach 不免疫)。

      13. start 返回值不能与 PTHREAD_CANCELED 相同——否则 join 时误判为被取消;NPTL 中 PTHREAD_CANCELED 是 ((void *) -1)。

      14. pthread_join 已 join 的 TID 结果未定义——可能 join 到重用 ID 的新线程;务必用专用变量保存 TID。

      15. 线程栈都在同一 VA 空间——可能交错;栈地址不能用绝对值;调试时需注意多栈交错导致的复杂栈回溯。

      16. 线程创建比进程快约 10 倍——基于 clone(CLONE_VM|…​);无需复制页表 + 无 COW 标记。

      17. 线程共享 fd 偏移与状态标志——多线程同时 read/write 同一 fd → 竞争条件;线程间同步 fd 访问需 mutex 或独立 fd 副本(dup)。

      18. 大量线程受限于 VA 空间——x86-32 典型 3 GB VA;每线程默认栈 8 MB + 线程特定数据;可定制 stacksize 减小占用。

        • 跨章衔接:第 30 章线程同步(mutex/cond)→ 解决共享数据竞争;第 31 章 thread-safety 与 per-thread storage → 解决 errno/可重入;第 32 章线程取消 → pthread_cancel + cleanup handlers;第 33 章线程细节 → 信号交互、LinuxThreads vs NPTL;第 35 章调度 → 实时线程 SCHED_FIFO/RR;第 60 章 → 服务器设计模式(线程 vs 进程)。