Linux 线程分离与主动取消:pthread_detach、pthread_cancel
课程:尚硅谷《嵌入式 Linux 应用层开发》第 4 章线程处理
依据:2026-09-28 19:54 录音转写;课程 PDF 第 128 页。
范围说明:PDF 第 128 页列出线程终止方式并开始介绍
pthread_exit;本次录音主要演示pthread_detach、默认延迟取消、异步取消和禁止取消,属于对第 128 页线程终止主题的实操补充。
1. 本节解决两个问题
问题一:不想等待线程,怎样让它结束后自动回收?
使用:
c
pthread_detach(thread_id);
问题二:想让正在运行的线程提前停止,怎样请求取消?
使用:
c
pthread_cancel(thread_id);
pthread_join(thread_id, &result);
这两种机制用途不同:
| 机制 | 解决的问题 | 是否让线程立刻停止 |
|---|---|---|
pthread_detach |
线程结束后由系统自动回收 | 否 |
pthread_cancel |
向目标线程提出取消请求 | 不一定 |
2. 不要把工作线程叫作"子线程"
fork 创建父子进程,父子进程有明确的亲缘关系。Pthreads 中,同一进程的线程通常被视为对等线程。
为了便于描述,可以说"主线程"和"工作线程"。"子线程"是常见口语,但不要由此推断线程具有与父子进程完全相同的生命周期关系。
3. Joinable 与 Detached 回顾
新创建的线程默认是 joinable:
#mermaid-svg-whPxV1fIGcXsInUi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-whPxV1fIGcXsInUi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-whPxV1fIGcXsInUi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-whPxV1fIGcXsInUi .error-icon{fill:#552222;}#mermaid-svg-whPxV1fIGcXsInUi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-whPxV1fIGcXsInUi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-whPxV1fIGcXsInUi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-whPxV1fIGcXsInUi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-whPxV1fIGcXsInUi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-whPxV1fIGcXsInUi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-whPxV1fIGcXsInUi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-whPxV1fIGcXsInUi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-whPxV1fIGcXsInUi .marker.cross{stroke:#333333;}#mermaid-svg-whPxV1fIGcXsInUi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-whPxV1fIGcXsInUi p{margin:0;}#mermaid-svg-whPxV1fIGcXsInUi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-whPxV1fIGcXsInUi .cluster-label text{fill:#333;}#mermaid-svg-whPxV1fIGcXsInUi .cluster-label span{color:#333;}#mermaid-svg-whPxV1fIGcXsInUi .cluster-label span p{background-color:transparent;}#mermaid-svg-whPxV1fIGcXsInUi .label text,#mermaid-svg-whPxV1fIGcXsInUi span{fill:#333;color:#333;}#mermaid-svg-whPxV1fIGcXsInUi .node rect,#mermaid-svg-whPxV1fIGcXsInUi .node circle,#mermaid-svg-whPxV1fIGcXsInUi .node ellipse,#mermaid-svg-whPxV1fIGcXsInUi .node polygon,#mermaid-svg-whPxV1fIGcXsInUi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-whPxV1fIGcXsInUi .rough-node .label text,#mermaid-svg-whPxV1fIGcXsInUi .node .label text,#mermaid-svg-whPxV1fIGcXsInUi .image-shape .label,#mermaid-svg-whPxV1fIGcXsInUi .icon-shape .label{text-anchor:middle;}#mermaid-svg-whPxV1fIGcXsInUi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-whPxV1fIGcXsInUi .rough-node .label,#mermaid-svg-whPxV1fIGcXsInUi .node .label,#mermaid-svg-whPxV1fIGcXsInUi .image-shape .label,#mermaid-svg-whPxV1fIGcXsInUi .icon-shape .label{text-align:center;}#mermaid-svg-whPxV1fIGcXsInUi .node.clickable{cursor:pointer;}#mermaid-svg-whPxV1fIGcXsInUi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-whPxV1fIGcXsInUi .arrowheadPath{fill:#333333;}#mermaid-svg-whPxV1fIGcXsInUi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-whPxV1fIGcXsInUi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-whPxV1fIGcXsInUi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-whPxV1fIGcXsInUi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-whPxV1fIGcXsInUi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-whPxV1fIGcXsInUi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-whPxV1fIGcXsInUi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-whPxV1fIGcXsInUi .cluster text{fill:#333;}#mermaid-svg-whPxV1fIGcXsInUi .cluster span{color:#333;}#mermaid-svg-whPxV1fIGcXsInUi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-whPxV1fIGcXsInUi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-whPxV1fIGcXsInUi rect.text{fill:none;stroke-width:0;}#mermaid-svg-whPxV1fIGcXsInUi .icon-shape,#mermaid-svg-whPxV1fIGcXsInUi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-whPxV1fIGcXsInUi .icon-shape p,#mermaid-svg-whPxV1fIGcXsInUi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-whPxV1fIGcXsInUi .icon-shape .label rect,#mermaid-svg-whPxV1fIGcXsInUi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-whPxV1fIGcXsInUi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-whPxV1fIGcXsInUi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-whPxV1fIGcXsInUi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pthread_join
创建 joinable 线程
线程执行结束
保留部分退出资源
资源回收并取得结果
把线程设为 detached 后:
#mermaid-svg-fovXPCHHKHzp0RzX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fovXPCHHKHzp0RzX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fovXPCHHKHzp0RzX .error-icon{fill:#552222;}#mermaid-svg-fovXPCHHKHzp0RzX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fovXPCHHKHzp0RzX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fovXPCHHKHzp0RzX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fovXPCHHKHzp0RzX .marker.cross{stroke:#333333;}#mermaid-svg-fovXPCHHKHzp0RzX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fovXPCHHKHzp0RzX p{margin:0;}#mermaid-svg-fovXPCHHKHzp0RzX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fovXPCHHKHzp0RzX .cluster-label text{fill:#333;}#mermaid-svg-fovXPCHHKHzp0RzX .cluster-label span{color:#333;}#mermaid-svg-fovXPCHHKHzp0RzX .cluster-label span p{background-color:transparent;}#mermaid-svg-fovXPCHHKHzp0RzX .label text,#mermaid-svg-fovXPCHHKHzp0RzX span{fill:#333;color:#333;}#mermaid-svg-fovXPCHHKHzp0RzX .node rect,#mermaid-svg-fovXPCHHKHzp0RzX .node circle,#mermaid-svg-fovXPCHHKHzp0RzX .node ellipse,#mermaid-svg-fovXPCHHKHzp0RzX .node polygon,#mermaid-svg-fovXPCHHKHzp0RzX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fovXPCHHKHzp0RzX .rough-node .label text,#mermaid-svg-fovXPCHHKHzp0RzX .node .label text,#mermaid-svg-fovXPCHHKHzp0RzX .image-shape .label,#mermaid-svg-fovXPCHHKHzp0RzX .icon-shape .label{text-anchor:middle;}#mermaid-svg-fovXPCHHKHzp0RzX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fovXPCHHKHzp0RzX .rough-node .label,#mermaid-svg-fovXPCHHKHzp0RzX .node .label,#mermaid-svg-fovXPCHHKHzp0RzX .image-shape .label,#mermaid-svg-fovXPCHHKHzp0RzX .icon-shape .label{text-align:center;}#mermaid-svg-fovXPCHHKHzp0RzX .node.clickable{cursor:pointer;}#mermaid-svg-fovXPCHHKHzp0RzX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fovXPCHHKHzp0RzX .arrowheadPath{fill:#333333;}#mermaid-svg-fovXPCHHKHzp0RzX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fovXPCHHKHzp0RzX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fovXPCHHKHzp0RzX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fovXPCHHKHzp0RzX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fovXPCHHKHzp0RzX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fovXPCHHKHzp0RzX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fovXPCHHKHzp0RzX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fovXPCHHKHzp0RzX .cluster text{fill:#333;}#mermaid-svg-fovXPCHHKHzp0RzX .cluster span{color:#333;}#mermaid-svg-fovXPCHHKHzp0RzX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fovXPCHHKHzp0RzX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fovXPCHHKHzp0RzX rect.text{fill:none;stroke-width:0;}#mermaid-svg-fovXPCHHKHzp0RzX .icon-shape,#mermaid-svg-fovXPCHHKHzp0RzX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fovXPCHHKHzp0RzX .icon-shape p,#mermaid-svg-fovXPCHHKHzp0RzX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fovXPCHHKHzp0RzX .icon-shape .label rect,#mermaid-svg-fovXPCHHKHzp0RzX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fovXPCHHKHzp0RzX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fovXPCHHKHzp0RzX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fovXPCHHKHzp0RzX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建线程
pthread_detach
线程继续执行
线程自然结束
系统自动回收线程资源
Detached 只改变回收方式:
- 不会停止线程;
- 不会让线程立即退出;
- 不能再
pthread_join; - 不能取得线程返回值;
- 不会让线程脱离所属进程。
4. pthread_detach 接口
c
#include <pthread.h>
int pthread_detach(pthread_t thread);
返回值:
- 成功返回
0; - 失败直接返回错误号。
c
int error = pthread_detach(thread_id);
if (error != 0) {
fprintf(stderr, "pthread_detach: %s\n",
strerror(error));
}
它是 Pthreads 函数,应使用返回值配合 strerror,而不是直接 perror。
5. Detached 线程的基础示例
下面的主线程不调用 pthread_join。它把工作线程设为 detached 后,调用 pthread_exit 只结束主线程,让工作线程继续运行。
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void *worker(void *arg)
{
(void)arg;
puts("工作线程开始");
sleep(2);
puts("工作线程自然结束,资源将自动回收");
return NULL;
}
int main(void)
{
pthread_t thread_id;
int error = pthread_create(&thread_id, NULL, worker, NULL);
if (error != 0) {
fprintf(stderr, "pthread_create: %s\n",
strerror(error));
return EXIT_FAILURE;
}
error = pthread_detach(thread_id);
if (error != 0) {
fprintf(stderr, "pthread_detach: %s\n",
strerror(error));
pthread_join(thread_id, NULL);
return EXIT_FAILURE;
}
puts("主线程不等待工作线程结果");
pthread_exit(NULL);
}
编译运行:
bash
gcc -Wall -Wextra -pthread detach_test.c -o detach_test
./detach_test
5.1 为什么主线程不能直接 return
从 main 返回会结束整个进程,Detached 工作线程也会被一起终止。
text
main return / exit → 整个进程结束 → 所有线程结束
pthread_exit → 只结束调用线程 → 其他线程可继续
录音用"主线程 sleep(3),保证工作线程先结束"演示现象,但这只是时间上的猜测。更清楚的演示方式是让主线程调用 pthread_exit(NULL)。
5.2 Detached 适合什么场景
- 不关心返回值;
- 不需要等待完成时刻;
- 线程能独立管理参数和资源;
- 进程有明确的整体生命周期管理。
若主线程退出前必须确保任务完成,就应该使用 pthread_join,不应 detach 后再用 sleep 猜测。
6. pthread_cancel 只是发送取消请求
c
#include <pthread.h>
int pthread_cancel(pthread_t thread);
调用成功表示请求已发送:
c
int error = pthread_cancel(thread_id);
它不保证目标线程已经停止。目标线程是否以及何时响应,取决于:
- 取消状态是否启用;
- 取消类型是延迟还是异步;
- 延迟取消时是否到达取消点。
正确确认流程:
工作线程 主线程 工作线程 主线程 #mermaid-svg-tN7HmwO0ASYBi6dl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tN7HmwO0ASYBi6dl .error-icon{fill:#552222;}#mermaid-svg-tN7HmwO0ASYBi6dl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tN7HmwO0ASYBi6dl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tN7HmwO0ASYBi6dl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tN7HmwO0ASYBi6dl .marker.cross{stroke:#333333;}#mermaid-svg-tN7HmwO0ASYBi6dl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tN7HmwO0ASYBi6dl p{margin:0;}#mermaid-svg-tN7HmwO0ASYBi6dl .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN7HmwO0ASYBi6dl text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN7HmwO0ASYBi6dl .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tN7HmwO0ASYBi6dl .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tN7HmwO0ASYBi6dl #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tN7HmwO0ASYBi6dl .sequenceNumber{fill:white;}#mermaid-svg-tN7HmwO0ASYBi6dl #sequencenumber{fill:#333;}#mermaid-svg-tN7HmwO0ASYBi6dl #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tN7HmwO0ASYBi6dl .messageText{fill:#333;stroke:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN7HmwO0ASYBi6dl .labelText,#mermaid-svg-tN7HmwO0ASYBi6dl .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .loopText,#mermaid-svg-tN7HmwO0ASYBi6dl .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tN7HmwO0ASYBi6dl .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tN7HmwO0ASYBi6dl .noteText,#mermaid-svg-tN7HmwO0ASYBi6dl .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tN7HmwO0ASYBi6dl .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN7HmwO0ASYBi6dl .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN7HmwO0ASYBi6dl .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tN7HmwO0ASYBi6dl .actorPopupMenu{position:absolute;}#mermaid-svg-tN7HmwO0ASYBi6dl .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tN7HmwO0ASYBi6dl .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tN7HmwO0ASYBi6dl .actor-man circle,#mermaid-svg-tN7HmwO0ASYBi6dl line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tN7HmwO0ASYBi6dl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pthread_cancel,发送请求 到达取消点并执行清理 线程终止 pthread_join PTHREAD_CANCELED
7. 默认取消方式:启用、延迟
线程默认设置为:
text
取消状态:PTHREAD_CANCEL_ENABLE
取消类型:PTHREAD_CANCEL_DEFERRED
也就是说,线程允许被取消,但通常只在到达取消点时真正响应。
常见取消点包括:
sleep、nanosleep;read;pthread_cond_wait;pthread_join;pthread_testcancel。
纯计算循环若始终不到取消点,可能长时间不响应请求:
c
for (;;) {
do_some_calculation();
pthread_testcancel();
}
8. 默认延迟取消的完整示例
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void cleanup(void *arg)
{
(void)arg;
puts("工作线程执行清理函数");
}
static void *worker(void *arg)
{
(void)arg;
pthread_cleanup_push(cleanup, NULL);
for (;;) {
puts("working...");
sleep(1); /* 取消点 */
}
pthread_cleanup_pop(1);
return NULL;
}
int main(void)
{
pthread_t thread_id;
int error = pthread_create(&thread_id, NULL, worker, NULL);
if (error != 0) {
fprintf(stderr, "pthread_create: %s\n",
strerror(error));
return EXIT_FAILURE;
}
sleep(3);
error = pthread_cancel(thread_id);
if (error != 0) {
fprintf(stderr, "pthread_cancel: %s\n",
strerror(error));
return EXIT_FAILURE;
}
void *result = NULL;
error = pthread_join(thread_id, &result);
if (error != 0) {
fprintf(stderr, "pthread_join: %s\n",
strerror(error));
return EXIT_FAILURE;
}
if (result == PTHREAD_CANCELED) {
puts("主线程确认:工作线程已经被取消");
}
return EXIT_SUCCESS;
}
编译运行:
bash
gcc -Wall -Wextra -pthread cancel_test.c -o cancel_test
./cancel_test
8.1 逐步理解
| 语句 | 作用 |
|---|---|
pthread_cleanup_push |
注册线程取消时需要执行的清理函数。 |
sleep(1) |
本例的取消点,待定取消请求可在这里生效。 |
pthread_cancel |
主线程发送取消请求。 |
pthread_join |
等待目标线程真正结束并回收资源。 |
result == PTHREAD_CANCELED |
确认线程是因为取消而结束。 |
pthread_cleanup_push/pop 在很多实现中是宏,必须在同一词法作用域中成对出现。
9. 禁止取消:pthread_setcancelstate
c
int pthread_setcancelstate(int state, int *oldstate);
临时禁止取消:
c
int old_state;
int error = pthread_setcancelstate(
PTHREAD_CANCEL_DISABLE, &old_state);
恢复先前状态:
c
error = pthread_setcancelstate(old_state, NULL);
9.1 请求不会必然消失
线程处于 PTHREAD_CANCEL_DISABLE 时收到取消请求,请求可能保持待处理。线程以后重新启用取消并到达取消点时,仍可能被取消。
所以录音中的"禁止取消后永远不会被外部取消"不准确。更准确地说:禁止期间不会执行取消;重新启用后必须考虑待处理请求。
9.2 适合临时禁止取消的区域
text
关闭取消
→ 修改必须保持一致的共享状态
→ 恢复原取消状态
→ 到安全位置检查取消请求
如果代码已经持有互斥锁,更常见的做法是注册取消清理函数,保证取消时解锁。
10. 修改取消类型:pthread_setcanceltype
c
int pthread_setcanceltype(int type, int *oldtype);
10.1 延迟取消
c
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
这是默认方式。线程只在可控的取消点退出,便于安排清理。
10.2 异步取消
c
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
异步取消可能在几乎任意指令位置发生。例如线程可能正在:
- 持有互斥锁;
- 修改一半共享结构体;
- 更新内存分配器内部状态;
- 操作文件和标准 I/O 内部状态。
此时突然结束容易造成死锁、内存破坏或资源泄漏。因此录音中的异步取消代码只用于观察现象,普通业务代码应保持默认的延迟取消。
打印计数每次停在不同位置,只能说明取消时机不可预测,不能说明异步取消适合实际使用。
11. Detach 与 Cancel 能否一起使用
可以向 detached 线程发送取消请求,但不能再 pthread_join 它,也就无法通过 PTHREAD_CANCELED 确认结束。
基础阶段建议把两种实验分开:
text
自然结束、不关心结果 → detach
需要主动取消并确认结束 → 保持 joinable,cancel 后 join
需要等待取消完成时,不要先 detach。
12. 更推荐的停止方式:协作退出
对于普通业务线程,共享停止标志配合条件变量通常比取消更容易控制:
#mermaid-svg-pC34v1V6yoKOvaLb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pC34v1V6yoKOvaLb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pC34v1V6yoKOvaLb .error-icon{fill:#552222;}#mermaid-svg-pC34v1V6yoKOvaLb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pC34v1V6yoKOvaLb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pC34v1V6yoKOvaLb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pC34v1V6yoKOvaLb .marker.cross{stroke:#333333;}#mermaid-svg-pC34v1V6yoKOvaLb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pC34v1V6yoKOvaLb p{margin:0;}#mermaid-svg-pC34v1V6yoKOvaLb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pC34v1V6yoKOvaLb .cluster-label text{fill:#333;}#mermaid-svg-pC34v1V6yoKOvaLb .cluster-label span{color:#333;}#mermaid-svg-pC34v1V6yoKOvaLb .cluster-label span p{background-color:transparent;}#mermaid-svg-pC34v1V6yoKOvaLb .label text,#mermaid-svg-pC34v1V6yoKOvaLb span{fill:#333;color:#333;}#mermaid-svg-pC34v1V6yoKOvaLb .node rect,#mermaid-svg-pC34v1V6yoKOvaLb .node circle,#mermaid-svg-pC34v1V6yoKOvaLb .node ellipse,#mermaid-svg-pC34v1V6yoKOvaLb .node polygon,#mermaid-svg-pC34v1V6yoKOvaLb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pC34v1V6yoKOvaLb .rough-node .label text,#mermaid-svg-pC34v1V6yoKOvaLb .node .label text,#mermaid-svg-pC34v1V6yoKOvaLb .image-shape .label,#mermaid-svg-pC34v1V6yoKOvaLb .icon-shape .label{text-anchor:middle;}#mermaid-svg-pC34v1V6yoKOvaLb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pC34v1V6yoKOvaLb .rough-node .label,#mermaid-svg-pC34v1V6yoKOvaLb .node .label,#mermaid-svg-pC34v1V6yoKOvaLb .image-shape .label,#mermaid-svg-pC34v1V6yoKOvaLb .icon-shape .label{text-align:center;}#mermaid-svg-pC34v1V6yoKOvaLb .node.clickable{cursor:pointer;}#mermaid-svg-pC34v1V6yoKOvaLb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pC34v1V6yoKOvaLb .arrowheadPath{fill:#333333;}#mermaid-svg-pC34v1V6yoKOvaLb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pC34v1V6yoKOvaLb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pC34v1V6yoKOvaLb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pC34v1V6yoKOvaLb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pC34v1V6yoKOvaLb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pC34v1V6yoKOvaLb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pC34v1V6yoKOvaLb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pC34v1V6yoKOvaLb .cluster text{fill:#333;}#mermaid-svg-pC34v1V6yoKOvaLb .cluster span{color:#333;}#mermaid-svg-pC34v1V6yoKOvaLb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pC34v1V6yoKOvaLb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pC34v1V6yoKOvaLb rect.text{fill:none;stroke-width:0;}#mermaid-svg-pC34v1V6yoKOvaLb .icon-shape,#mermaid-svg-pC34v1V6yoKOvaLb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pC34v1V6yoKOvaLb .icon-shape p,#mermaid-svg-pC34v1V6yoKOvaLb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pC34v1V6yoKOvaLb .icon-shape .label rect,#mermaid-svg-pC34v1V6yoKOvaLb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pC34v1V6yoKOvaLb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pC34v1V6yoKOvaLb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pC34v1V6yoKOvaLb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 主线程设置 stop=true
唤醒条件变量
工作线程发现停止请求
完成当前步骤并清理
return
主线程 join
示意代码:
c
while (!stop_requested) {
wait_for_or_process_task();
}
cleanup_resources();
return NULL;
停止标志本身也必须通过互斥锁、原子变量或其他同步方式访问,不能让多个线程无保护地读写普通变量。
13. 录音示例中的顺序问题
录音使用 sleep 让主线程晚于工作线程结束,以展示 detached 线程输出。这只能让某次实验更容易看到预期顺序,不能构成可靠同步。
text
sleep:大致延时,依赖调度和机器速度
join:明确等待目标线程结束
pthread_exit:只结束调用线程,让其他线程继续
条件变量:等待某个共享条件成立
真正需要保证先后关系时,应选择表达真实条件的同步方法。
14. 常见错误
| 错误 | 后果 | 正确做法 |
|---|---|---|
| detach 后再 join | pthread_join 失败 |
创建时明确选择一种回收方式 |
| main 直接返回,期待 detached 线程继续 | 整个进程结束 | 管理进程生命周期,或主线程 pthread_exit |
用 sleep 保证线程完成 |
时序依赖运行环境 | 需要完成保证时使用 pthread_join |
pthread_cancel 返回 0 就释放目标资源 |
目标线程可能仍在运行 | pthread_join 确认终止 |
| 纯计算循环没有取消点 | 延迟取消长期不生效 | 合适位置调用 pthread_testcancel |
| 使用异步取消 | 锁未释放、数据损坏 | 保持延迟取消并设置清理函数 |
| 禁止取消后不恢复旧状态 | 线程以后无法按设计响应取消 | 保存并恢复 old_state |
| 把线程强制取消当作首选退出方式 | 资源管理困难 | 优先设计协作式退出 |
15. 教材与录音表述校正
pthread_detach不等待线程,也不终止线程;它只让线程结束后自动回收资源。- Detached 线程仍属于原进程;主线程从
main返回会结束整个进程和所有线程。 sleep不能保证 detached 工作线程一定完成,只适合演示观察。pthread_cancel是取消请求,返回 0 不表示目标线程已经退出。- 默认是启用取消和延迟取消,通常到取消点才执行取消。
- 禁止取消期间的请求可能保持待处理,恢复取消后仍可能生效。
- 异步取消的停止位置不可预测,实际程序通常不应使用。
- 要确认 joinable 线程因取消结束,应
pthread_join并比较PTHREAD_CANCELED。
16. 本节最低掌握标准
学完后应能回答:
pthread_detach会不会结束线程?- Detached 线程能否再
pthread_join? - 为什么主线程直接
return会影响 detached 线程? pthread_cancel返回 0 表示什么?- 怎样确认线程确实因取消而结束?
- 默认取消状态和类型分别是什么?
- 什么是取消点?
- 禁止取消期间收到的请求会怎样?
- 为什么异步取消危险?
- 哪些场景更适合协作式停止?
最低实践要求:
- 创建一个线程并设置为 detached,让主线程调用
pthread_exit; - 创建一个默认延迟取消线程;
- 调用
pthread_cancel后用pthread_join确认PTHREAD_CANCELED; - 在纯计算循环中加入
pthread_testcancel; - 能说明为什么实验代码不应依赖
sleep保证先后顺序。
17. PDF 页码与录音时间索引
| 主题 | PDF 页码 | 录音时间 |
|---|---|---|
| 线程终止方式总览 | 128 | 00:02---00:15、04:48---05:12 |
pthread_detach 与不等待回收 |
128主题的录音补充 | 00:16---04:47 |
默认延迟取消、cancel + join |
128主题的录音补充 | 04:49---09:30 |
| 异步取消演示 | 128主题的录音补充 | 09:31---11:43 |
| 禁止取消演示 | 128主题的录音补充 | 11:44---13:00 |
进一步核对资料:
- Linux man-pages:pthread_detach(3)、pthread_cancel(3)、pthread_join(3)、pthread_testcancel(3)。