第 32 章 线程:线程取消 (Threads: Thread Cancellation)

      +

      核心结论

      • pthread_cancel() 发送取消请求:pthread_cancel(target) 仅发送请求,立即返回;目标线程在合适的取消点才真正终止。

      • 取消状态 + 类型决定取消时机:state ENABLE(默认)vs DISABLE;type DEFERRED(默认)vs ASYNCHRONOUS;DISABLE 时请求挂起直到 ENABLE;DEFERRED 时只在取消点才响应。

      • 取消点是 SUSv3 指定的函数集:约 40+ 函数必须为取消点(accept/read/write/sleep/pthread_cond_wait/pthread_join 等);其他实现可定义额外取消点(如多数 stdio 函数)。

      • pthread_testcancel() 是显式取消点:用于 compute-bound 循环(无内置取消点);线程有 pending cancel 时调用立即终止。

      • cleanup handlers 是 LIFO 栈:pthread_cleanup_push/pop 管理;线程被取消时按 LIFO 顺序自动调用;pthread_exit 也触发未 pop 的 cleanup handler;简单 return 不触发。

      • 异步取消很少有用:ASYNCHRONOUS 可在任何指令取消——cleanup handler 无法确定线程状态;不能 malloc/lock mutex;只适用于 cancel compute-bound 循环。

      • canceled 线程 join 返回 PTHREAD_CANCELED:注意不要让正常线程 return 值与 PTHREAD_CANCELED 相同。

      本章主旨

      本章深入 POSIX 线程取消机制——主动请求另一线程终止的同步原语。读者应掌握:pthread_cancel 语义(仅发请求,不等待)、state/type 控制(ENABLE/DISABLE × DEFERRED/ASYNCHRONOUS)、取消点概念(表 32-1 SUSv3 强制取消点)、pthread_testcancel 显式取消点、cleanup handler 栈(LIFO 自动调用)、pthread_exit 也触发未 pop 的 handler。理解「异步取消为何少用」——handler 无法确定线程状态、不能调 malloc/lock——是避免取消机制陷阱的关键。thread_cleanup.c 示例展示完整的 cleanup 模式:push → 操作 → pop(1) 即使非 cancel 也执行清理。

      一、核心概念

      本章围绕 6 个核心概念展开:从 pthread_cancel 基础入手,到 state/type 控制、取消点、pthread_testcancel 显式取消点、cleanup handlers、异步取消的危险性。

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

      pthread_cancel() 取消请求

      pthread_cancel(target) 向 target 发取消请求;立即返回;target 是否及何时终止取决于其 state + type + 是否到达取消点。

      §32.1;不等待 target;返回 0 成功或正错误码;与 pthread_kill(发信号) 不同——cancel 是同步语义。

      取消 state 与 type

      state: ENABLE(默认,可取消)/ DISABLE(屏蔽,请求挂起);type: DEFERRED(默认,到取消点才响应)/ ASYNCHRONOUS(任何指令)。DISABLE 时请求 pending;ENABLE 时 type 决定时机。

      §32.2;pthread_setcancelstate/type 设置;返回 old 值(Linux 允许 NULL 但 SUSv3 不强制);fork 继承 state/type;exec 重置为 ENABLE+DEFERRED。

      取消点(cancellation points)

      SUSv3 指定约 40+ 函数为必须取消点(accept/close/connect/read/write/sleep/pthread_cond_wait/pthread_join 等);任何可能阻塞的函数都是候选。

      §32.3;表 32-1;其他实现可定义额外取消点(如 stdio/dlopen/syslog/popen/semop);SUSv4 新增 openat、移除 sigpause/usleep。

      pthread_testcancel()

      显式取消点;线程有 pending cancel 时调用立即终止;用于 compute-bound 循环(无内置取消点)。

      §32.4;建议在长时间无阻塞操作的代码中周期性调用;不是取消请求时调用无副作用。

      cleanup handlers

      LIFO 栈的清理函数;pthread_cleanup_push 注册、pthread_cleanup_pop 移除(execute 非 0 时执行);线程被 cancel 或 pthread_exit 时按 LIFO 顺序自动调用。

      §32.5;常用于 free 内存、unlock mutex、恢复全局状态;pthread_cleanup_push/pop 可能实现为宏——必须同 lexical scope 配对;push/pop 间声明的变量作用域限于该 scope。

      异步取消的危险

      ASYNCHRONOUS 取消可在任意指令——handler 无法判断线程状态;不能 malloc/lock mutex;只在 cancel compute-bound 循环时有用。

      §32.6;SUSv3 例外:pthread_cancel/setcancelstate/setcanceltype 是 async-cancel-safe。

      二、详细笔记

      32.1 pthread_cancel() 取消请求

      Whatpthread_cancel(thread) 向指定线程发送取消请求;立即返回,不等待目标线程终止。

      Why:用于「主线程通知 worker 线程提前终止」场景——错误检测、GUI 取消按钮、超时控制。

      How

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

      要点:

      • 返回 0 成功 / 正错误码失败。

      • 不等待目标线程终止——目标可能在任意时刻响应。

      • 目标线程响应行为由 state + type 决定(§32.2)。

      • 多次 cancel 对同一线程:除首次外一般忽略——cancel 不是信号,不排队。

      • canceled 线程 join 时返回 PTHREAD_CANCELED。

      When:worker 线程做长时间任务;GUI 应用响应用户取消;超时处理;多线程协调退出。

      Example

      // 摘自《The Linux Programming Interface》第 32 章 — Listing 32-1
      // 摘自 threads/thread_cancel.c
      pthread_t thr;
      pthread_create(&thr, NULL, threadFunc, NULL);
      sleep(3);
      pthread_cancel(thr);
      pthread_join(thr, &res);
      if (res == PTHREAD_CANCELED)
          printf("Thread was canceled\n");

      32.2 取消状态与类型

      What:state (ENABLE/DISABLE) + type (DEFERRED/ASYNCHRONOUS) 控制线程如何响应 cancel 请求。

      Why:线程需控制取消的精确时机——某些 critical section 不能被取消(DISABLE);某些场景希望立即取消(ASYNCHRONOUS)。

      How

      // 摘自《The Linux Programming Interface》第 32 章
      #include <pthread.h>
      int pthread_setcancelstate(int state, int *oldstate);
      int pthread_setcanceltype(int type, int *oldtype);

      state 取值:

      • PTHREAD_CANCEL_ENABLE(默认):线程可取消。

      • PTHREAD_CANCEL_DISABLE:线程不可取消;cancel 请求挂起(pending)直到 ENABLE。

      type 取值:

      • PTHREAD_CANCEL_DEFERRED(默认):取消延迟到下一个取消点。

      • PTHREAD_CANCEL_ASYNCHRONOUS:任何指令都可能取消(罕见使用,§32.6)。

      DISABLE 用途:

      • 临界区不能被取消——例如持有 mutex 时被取消 → mutex 永久死锁。

      • pthread_cleanup_push/pop 内的 cleanup handler 中不能再次取消。

      • 不能调 malloc/lock 的区域(避免部分完成的资源分配)。

      继承规则(§32.2):

      • fork:子继承调用线程的 state + type。

      • exec:新程序主线程的 state + type 重置为 ENABLE + DEFERRED。

      old 参数(§32.2):

      • oldstate/oldtype 返回前值。

      • Linux 允许 NULL 跳过;SUSv3 不强制,可移植代码应传非 NULL。

      When:持锁时 DISABLE cancel;调外部库时考虑是否需要 DISABLE;compute-bound 循环 ASYNCHRONOUS cancel(罕见)。

      Example

      // 摘自《The Linux Programming Interface》第 32 章
      int oldstate;
      pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
      /* 临界区 — 不能取消 */
      process_critical_section();
      pthread_setcancelstate(oldstate, NULL);  /* 恢复 */

      32.3 取消点

      What:取消点是 SUSv3 指定的一组函数;线程在取消点检查 pending cancel 请求,若有则执行取消(含 cleanup handlers)。

      Why:在「可能阻塞」处检查取消——避免线程永久运行不被响应;确保取消时线程处于「安全」状态(不是执行任意指令)。

      How

      SUSv3 强制取消点(§32.3,表 32-1):

      • I/O:accept(), close(), connect(), creat(), fsync(), fdatasync(), msync(), open(), pread(), pwrite(), read(), readv(), recv(), recvfrom(), recvmsg(), select(), poll(), pselect(), send(), sendmsg(), sendto(), write(), writev()

      • fcntl:fcntl(F_SETLKW)lockf(F_LOCK)

      • IPC:aio_suspend(), mq_receive(), mq_send(), mq_timedreceive(), mq_timedsend(), msgrcv(), msgsnd(), sem_wait(), sem_timedwait()

      • 信号/等待:pause(), sigsuspend(), sigwait(), sigwaitinfo(), sigtimedwait(), sleep(), usleep(), nanosleep(), clock_nanosleep(), wait(), waitid(), waitpid()

      • stdio(部分):tcdrain(), getmsg(), getpmsg(), putmsg(), putpmsg()

      • Pthreads:pthread_cond_wait(), pthread_cond_timedwait(), pthread_join(), pthread_testcancel(), system()

      实现可定义额外取消点(§32.3):

      • stdio 函数。

      • dlopen() API。

      • syslog() API。

      • nftw(), popen(), semop(), unlink()

      • 读 utmp 等系统文件信息的函数。

      SUSv4 变化(§32.3):

      • 新增 openat() 为强制取消点。

      • 移除 sigpause()(移到 may-be 列表)。

      • 移除 usleep()(已从标准删除)。

      非取消点(§32.3):

      • SUSv3 明确规定:除强制 + may-be 列表外,其他函数不是取消点。

      • 可移植代码可假定「非列表函数不引发 cancel」。

      注意(§32.3 末):

      • glibc 标记许多非标准函数为取消点(任何可能阻塞的函数)。

      • write 等系统调用即使返回(非阻塞 I/O 情况)也可能是取消点。

      When:在 critical section 内尽量用非取消点函数;写长 compute-bound 循环加 pthread_testcancel。

      Example

      // 摘自《The Linux Programming Interface》第 32 章 — Listing 32-1
      for (j = 1; ; j++) {
          printf("Loop %d\n", j);   /* 可能取消点 */
          sleep(1);                  /* 取消点 — 1 秒后响应 */
      }

      32.4 pthread_testcancel() 显式取消点

      Whatpthread_testcancel() 是无副作用的取消点;若有 pending cancel 则立即终止。

      Why:compute-bound 循环无内置取消点;pthread_testcancel 提供显式响应机会。

      How

      // 摘自《The Linux Programming Interface》第 32 章
      #include <pthread.h>
      void pthread_testcancel(void);

      要点:

      • 无 pending cancel 时调用无副作用——可直接放在循环内。

      • pending cancel 时调用立即终止(含 cleanup handlers)。

      • 适合 compute-bound 循环、长时间 CPU 操作。

      When:写 CPU 密集循环;在关键路径外响应取消请求。

      Example

      // 摘自《The Linux Programming Interface》第 32 章
      void *compute_worker(void *arg) {
          while (work_remaining()) {
              do_compute_intensive_step();
              pthread_testcancel();   /* 周期性检查 cancel */
          }
          return NULL;
      }

      32.5 cleanup handlers

      What:LIFO 栈的清理函数;线程被 cancel 或 pthread_exit 时按 LIFO 顺序自动调用;pthread_cleanup_pop(execute) 可手动触发。

      Why:cancel 可能让线程持有锁或内存未释放——cleanup handler 保证资源清理,避免死锁/泄漏。

      How

      API(§32.5):

      // 摘自《The Linux Programming Interface》第 32 章
      #include <pthread.h>
      void pthread_cleanup_push(void (*routine)(void *), void *arg);
      void pthread_cleanup_pop(int execute);

      handler 签名:

      • void routine(void *arg):参数是 pthread_cleanup_push 时的 arg。

      行为:

      • pthread_cleanup_push:handler 入栈顶。

      • pthread_cleanup_pop(0):handler 出栈,不执行。

      • pthread_cleanup_pop(non-zero):handler 出栈并执行。

      • 线程被 cancel 时:栈中所有未 pop 的 handler 按 LIFO 自动执行,然后线程终止。

      • pthread_exit 时:同上——所有未 pop 的 handler 自动执行。

      • 简单 return 时:不自动执行未 pop 的 handler(仅 cancel/pthread_exit 触发)。

      实现注意(§32.5):

      • SUSv3 允许 push/pop 实现为宏——可能扩展为 {…​}

      • push/pop 必须同 lexical scope 配对——不能在 if 内 push、else 内 pop。

      • push 与 pop 之间声明的变量作用域限于 push/pop scope。

      使用模式:

      // 摘自《The Linux Programming Interface》第 32 章
      pthread_cleanup_push(cleanupHandler, arg);
      /* 临界操作 */
      if (error) pthread_exit((void *) -1);   /* cleanup 自动触发 */
      pthread_cleanup_pop(0);                  /* 正常完成,不需 cleanup */
      /* 或:pthread_cleanup_pop(1); 正常完成也执行 cleanup */

      thread_cleanup 示例(§32.5,Listing 32-2):

      // 摘自《The Linux Programming Interface》第 32 章 — Listing 32-2
      // 摘自 threads/thread_cleanup.c
      static void cleanupHandler(void *arg) {
          free(arg);                           /* q */
          pthread_mutex_unlock(&mtx);          /* w */
      }
      static void *threadFunc(void *arg) {
          void *buf = malloc(0x10000);         /* e 非取消点 */
          pthread_mutex_lock(&mtx);            /* r 非取消点 */
          pthread_cleanup_push(cleanupHandler, buf);  /* t */
          while (glob == 0)
              pthread_cond_wait(&cond, &mtx);  /* y 取消点 */
          pthread_cleanup_pop(1);              /* u 执行 cleanup */
          return NULL;
      }

      两种触发方式:

      1. 主线程调 pthread_cancel → 线程在 pthread_cond_wait 响应 cancel → cleanup handler 自动执行。

      2. 主线程设 glob=1 + signal cond → 线程正常退出循环 → pthread_cleanup_pop(1) 显式执行 cleanup。

      When:临界区内持有锁/内存 → push handler;pthread_exit 返回前清理资源。

      Example:见上述 threadFunc。

      32.6 异步取消的危险

      WhatPTHREAD_CANCEL_ASYNCHRONOUS 允许线程在任何机器指令取消;cleanup handler 无法判断线程状态。

      Why:了解异步取消的陷阱是决定是否使用它的前提;几乎所有场景应避免 ASYNCHRONOUS。

      How

      问题(§32.6):

      • 异步取消时机不可预测——可能在 malloc 中、可能在 lock mutex 后、可能在修改关键变量前。

      • cleanup handler 无法判断「线程已分配哪些资源、已 lock 哪些 mutex」。

      • SUSv3 列出 async-cancel-safe 函数极少:仅 pthread_cancel/pthread_setcancelstate/pthread_setcanceltype。

      • 调其他函数(如 malloc/lock mutex)→ chaos:可能内存泄漏、永久死锁、数据不一致。

      一般原则(§32.6):

      • 异步取消线程不能分配资源(malloc)、不能获取锁(mutex/semaphore/file lock)。

      • 不能调绝大多数 Pthreads 函数。

      • 唯一适用场景:cancel compute-bound 循环(无锁、无 malloc)。

      When:避免 ASYNCHRONOUS;用 DEFERRED + pthread_testcancel 替代;只有 cancel 纯计算循环才考虑 ASYNCHRONOUS。

      Example:无——因为几乎不该用。

      三、关键图表

      取消 state + type 行为矩阵
      state type 行为

      DISABLE

      (忽略)

      cancel 请求挂起(pending)直到 ENABLE;后续按当时 type 处理

      ENABLE

      DEFERRED (默认)

      cancel 延迟到下一个取消点

      ENABLE

      ASYNCHRONOUS

      cancel 可在任何机器指令触发(罕见)

      默认:新线程 ENABLE + DEFERRED;fork 继承;exec 重置为 ENABLE + DEFERRED。

      SUSv3 强制取消点摘要(表 32-1 节选)
      类别 函数

      I/O

      accept, close, connect, read, readv, write, writev, recv, send, recvmsg, sendmsg, recvfrom, sendto, select, poll, pselect, pread, pwrite, open, creat, fsync, fdatasync, msync

      fcntl

      fcntl(F_SETLKW), lockf(F_LOCK)

      IPC

      mq_receive, mq_send, mq_timedreceive, mq_timedsend, msgrcv, msgsnd, sem_wait, sem_timedwait

      时间

      sleep, usleep, nanosleep, clock_nanosleep

      等待

      pause, sigsuspend, sigwait, sigwaitinfo, sigtimedwait, wait, waitid, waitpid

      Pthreads

      pthread_cond_wait, pthread_cond_timedwait, pthread_join, pthread_testcancel

      其他

      system, tcdrain

      实现可定义额外取消点(stdio/dlopen/syslog/nftw/popen/semop/unlink 等);SUSv4 新增 openat,移除 sigpause/usleep。

      cancel vs pthread_exit vs return 的 cleanup 行为
      退出方式 未 pop 的 cleanup handler 自动执行?

      pthread_cancel(响应取消)

      pthread_exit

      start 函数 return

      main return / exit()(任何线程)

      pthread_cleanup_pop(execute) 手动执行;execute 非零时执行 handler。

      四、思维导图

      mindmap
        root((第 32 章 线程取消))
          pthread cancel
            发取消请求
            立即返回
            不等目标终止
            多次 cancel 不排队
            PTHREAD CANCELED 返回值
          state type
            ENABLE 可取消 默认
            DISABLE 屏蔽 挂起
            DEFERRED 取消点 默认
            ASYNCHRONOUS 任意指令
            fork 继承 exec 重置
            oldstate oldtype 返回
          取消点
            SUSv3 表 32 1 强制
            I/O read write sleep
            pthread cond wait join
            可能阻塞函数候选
            SUSv4 openat 新增
            sigpause usleep 移除
          pthread testcancel
            显式取消点
            compute bound 循环用
            无副作用
          cleanup handlers
            LIFO 栈
            push pop
            pop execute 非零执行
            cancel pthread exit 自动
            return 不自动
            push pop 同 scope
            free unlock 清理
          异步取消
            任意指令取消
            handler 无法判断状态
            不能 malloc lock
            仅 pthread cancel setcancel state type 安全
            仅 compute bound 适用
            一般避免
          典型模式
            push 进入临界
            pop 0 正常退出
            pop 1 强制清理
            cancel 时自动
            持锁不取消 DISABLE

      五、重点与易错点

      1. pthread_cancel 仅发请求,立即返回——不等待目标线程;目标在合适的取消点才响应。

      2. 多次 cancel 不排队——除首次外一般忽略;cancel 不是信号,不支持挂起队列。

      3. state DISABLE 时 cancel 请求挂起——直到 ENABLE 才按当时 type 处理;持锁时 DISABLE 防止死锁。

      4. fork 继承 cancel state + type;exec 重置为 ENABLE + DEFERRED——避免新程序继承奇怪的 cancel 状态。

      5. 取消点是 SUSv3 强制的特定函数集——约 40+ 函数必须为取消点;其他实现可定义额外取消点;非列表函数假定不引发 cancel。

      6. DEFERRED 是默认 type——只在取消点响应;ASYNCHRONOUS 罕见且危险,几乎不用。

      7. pthread_testcancel 用于 compute-bound 循环——无内置取消点的代码周期性调用;无副作用。

      8. cleanup handler 是 LIFO 栈——push 入栈、pop 出栈;cancel/pthread_exit 时按 LIFO 自动执行;简单 return 不触发未 pop 的 handler。

      9. pthread_cleanup_push/pop 可能实现为宏——必须同 lexical scope 配对;不能在 if/else 分支配对。

      10. push 与 pop 之间声明的变量作用域限于该 scope(某些实现)——避免在 push/pop 间声明大块临时变量。

      11. pthread_cleanup_pop(execute) 中 execute 非零时执行 handler——用于「正常退出也清理」场景。

      12. pthread_cleanup_push 注册的 handler 在 pthread_exit 也执行——不只是 cancel;唯一不触发的退出方式是简单 return。

      13. canceled 线程 join 返回 PTHREAD_CANCELED——start 函数 return 值避免与 PTHREAD_CANCELED 相同(参考第 29 章陷阱)。

      14. 异步取消几乎无用——handler 无法判断线程状态、不能 malloc/lock;只适合 cancel 纯计算循环(罕见)。

      15. 持锁时禁用 cancel——否则线程被 cancel 时 mutex 永久死锁;典型模式 pthread_cleanup_push(unlock_mutex, &mtx) + DISABLE cancel + lock + ENABLE + 操作 + pop。

      16. pthread_cleanup_push 调用本身可展开为 {…​}——pop 必须在同 lexical scope;不在 if 分支内配对。

        • 跨章衔接:第 29 章线程基础 → 第 30 章 mutex/cond 同步 → 第 31 章线程安全与 TSD → 第 32 章线程取消(含 cleanup handler 与 cancel point);第 33 章线程细节 → cancel 与 async-cancel safety、pthread_atfork;第 21 章信号处理 → 信号 handler 必须 async-cancel-safe。