Linux 线程创建:pthread_create 与基本回收
课程:尚硅谷《嵌入式 Linux 应用层开发》第 4 章线程处理
依据:2026-09-28 19:21 录音转写;课程 PDF 第 124 页。
范围说明:PDF 第 124 页介绍线程概念并给出
pthread_create原型;录音继续讲了输入线程、输出线程和pthread_join,这些部分在本文中标为"录音补充"。
1. 线程是什么
进程是资源分配和程序运行的容器,线程是进程内部的执行单元。一个进程至少有一个线程,也可以同时运行多个线程。
#mermaid-svg-HK2MS50jiUgjYBXx{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-HK2MS50jiUgjYBXx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HK2MS50jiUgjYBXx .error-icon{fill:#552222;}#mermaid-svg-HK2MS50jiUgjYBXx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HK2MS50jiUgjYBXx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HK2MS50jiUgjYBXx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HK2MS50jiUgjYBXx .marker.cross{stroke:#333333;}#mermaid-svg-HK2MS50jiUgjYBXx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HK2MS50jiUgjYBXx p{margin:0;}#mermaid-svg-HK2MS50jiUgjYBXx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HK2MS50jiUgjYBXx .cluster-label text{fill:#333;}#mermaid-svg-HK2MS50jiUgjYBXx .cluster-label span{color:#333;}#mermaid-svg-HK2MS50jiUgjYBXx .cluster-label span p{background-color:transparent;}#mermaid-svg-HK2MS50jiUgjYBXx .label text,#mermaid-svg-HK2MS50jiUgjYBXx span{fill:#333;color:#333;}#mermaid-svg-HK2MS50jiUgjYBXx .node rect,#mermaid-svg-HK2MS50jiUgjYBXx .node circle,#mermaid-svg-HK2MS50jiUgjYBXx .node ellipse,#mermaid-svg-HK2MS50jiUgjYBXx .node polygon,#mermaid-svg-HK2MS50jiUgjYBXx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HK2MS50jiUgjYBXx .rough-node .label text,#mermaid-svg-HK2MS50jiUgjYBXx .node .label text,#mermaid-svg-HK2MS50jiUgjYBXx .image-shape .label,#mermaid-svg-HK2MS50jiUgjYBXx .icon-shape .label{text-anchor:middle;}#mermaid-svg-HK2MS50jiUgjYBXx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HK2MS50jiUgjYBXx .rough-node .label,#mermaid-svg-HK2MS50jiUgjYBXx .node .label,#mermaid-svg-HK2MS50jiUgjYBXx .image-shape .label,#mermaid-svg-HK2MS50jiUgjYBXx .icon-shape .label{text-align:center;}#mermaid-svg-HK2MS50jiUgjYBXx .node.clickable{cursor:pointer;}#mermaid-svg-HK2MS50jiUgjYBXx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HK2MS50jiUgjYBXx .arrowheadPath{fill:#333333;}#mermaid-svg-HK2MS50jiUgjYBXx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HK2MS50jiUgjYBXx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HK2MS50jiUgjYBXx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HK2MS50jiUgjYBXx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HK2MS50jiUgjYBXx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HK2MS50jiUgjYBXx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HK2MS50jiUgjYBXx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HK2MS50jiUgjYBXx .cluster text{fill:#333;}#mermaid-svg-HK2MS50jiUgjYBXx .cluster span{color:#333;}#mermaid-svg-HK2MS50jiUgjYBXx 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-HK2MS50jiUgjYBXx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HK2MS50jiUgjYBXx rect.text{fill:none;stroke-width:0;}#mermaid-svg-HK2MS50jiUgjYBXx .icon-shape,#mermaid-svg-HK2MS50jiUgjYBXx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HK2MS50jiUgjYBXx .icon-shape p,#mermaid-svg-HK2MS50jiUgjYBXx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HK2MS50jiUgjYBXx .icon-shape .label rect,#mermaid-svg-HK2MS50jiUgjYBXx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HK2MS50jiUgjYBXx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HK2MS50jiUgjYBXx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HK2MS50jiUgjYBXx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一个进程
共享资源
代码、全局变量、堆、文件描述符
主线程
独立栈和寄存器
线程 2
独立栈和寄存器
线程 3
独立栈和寄存器
线程之间共享大量资源,所以交换数据很方便;也正因为共享,多个线程同时读写同一个对象时可能产生数据竞争。
2. 线程共享什么,各自拥有什么
2.1 同一进程中的线程通常共享
- 代码段;
- 全局变量和静态变量;
- 堆内存;
- 打开的文件描述符;
- 当前工作目录;
- 进程 ID;
- 信号处理方式。
2.2 每个线程各自拥有
- 线程 ID,即
pthread_t; - 栈空间和局部自动变量;
- CPU 寄存器和执行位置;
- 信号屏蔽字;
errno;- 调度相关状态。
一个线程关闭共享的文件描述符后,其他线程也不能再使用该描述符。一个线程错误写坏堆内存,也可能让整个进程中的其他线程崩溃。
3. 线程与进程的基础对比
| 对比 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 各进程相互隔离 | 同一进程内共享 |
| 全局变量 | 默认不共享 | 默认共享 |
| 栈 | 每个进程有自己的栈 | 每个线程有自己的栈 |
| 文件描述符 | fork 后继承,但各进程有自己的描述符表 |
同一进程的线程共享描述符 |
| 数据交换 | 需要管道、共享内存、Socket 等 IPC | 可以直接访问共同变量 |
| 故障影响 | 通常隔离在一个进程 | 内存错误可能影响整个进程 |
| 同步需求 | 共享资源时需要同步 | 共同读写数据时经常需要互斥锁等同步 |
"线程比进程轻量"是一般性描述,不代表所有任务都应该使用线程。是否选择线程,还要考虑共享状态、故障隔离和程序结构。
4. 创建线程:pthread_create(PDF 124)
c
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);
4.1 四个参数
| 参数 | 含义 | 初学阶段常用写法 |
|---|---|---|
thread |
接收新线程的标识符 | &thread_id |
attr |
线程属性,如栈大小、分离状态 | NULL,使用默认属性 |
start_routine |
新线程开始执行的函数 | worker |
arg |
传给线程函数的唯一参数 | 数据地址或 NULL |
线程函数必须符合下面的形式:
c
void *worker(void *arg)
{
return NULL;
}
4.2 返回值
- 成功:返回
0; - 失败:直接返回错误号。
Pthreads 函数通常不会像普通系统调用那样通过 errno 报告失败。因此不能直接写:
c
if (pthread_create(...) != 0) {
perror("pthread_create");
}
正确写法:
c
int error = pthread_create(&thread_id, NULL, worker, NULL);
if (error != 0) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
}
5. pthread_t 应怎样理解
c
pthread_t thread_id;
它是 POSIX 线程库用于标识线程的类型。程序应把它当作不透明类型,不要依赖它一定是 long 或 unsigned long。
两个 pthread_t 是否表示同一线程,应使用:
c
if (pthread_equal(thread_a, thread_b)) {
/* 同一个线程 */
}
pthread_t 也不等同于 Linux 内核看到的线程 TID。日常 Pthreads 编程优先使用 Pthreads API 管理线程。
6. 创建以后谁先运行
pthread_create 成功后,主线程和新线程都具备运行条件。调度器决定谁先执行,程序不能假设固定顺序。
下面两种顺序都可能出现:
text
主线程先打印
工作线程后打印
text
工作线程先打印
主线程后打印
如果业务需要明确顺序,应使用 pthread_join、互斥锁、条件变量或其他同步方法,不能依赖打印顺序或 sleep。
7. 一个最小而完整的线程创建示例
下面创建两个工作线程,每个线程接收自己的参数并打印三次。线程之间不修改共同数据,因此当前阶段不会引入数据竞争。
c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct worker_arg {
int number;
const char *message;
};
static void *worker(void *arg)
{
struct worker_arg *info = arg;
for (int i = 0; i < 3; ++i) {
printf("线程 %d:%s,第 %d 次\n",
info->number, info->message, i + 1);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t thread1;
pthread_t thread2;
struct worker_arg arg1 = {1, "读取传感器"};
struct worker_arg arg2 = {2, "保存日志"};
int error = pthread_create(&thread1, NULL, worker, &arg1);
if (error != 0) {
fprintf(stderr, "创建线程 1 失败:%s\n", strerror(error));
return EXIT_FAILURE;
}
error = pthread_create(&thread2, NULL, worker, &arg2);
if (error != 0) {
fprintf(stderr, "创建线程 2 失败:%s\n", strerror(error));
pthread_join(thread1, NULL);
return EXIT_FAILURE;
}
error = pthread_join(thread1, NULL);
if (error != 0) {
fprintf(stderr, "等待线程 1 失败:%s\n", strerror(error));
return EXIT_FAILURE;
}
error = pthread_join(thread2, NULL);
if (error != 0) {
fprintf(stderr, "等待线程 2 失败:%s\n", strerror(error));
return EXIT_FAILURE;
}
puts("两个工作线程都已结束,主线程退出。");
return EXIT_SUCCESS;
}
编译:
bash
gcc -Wall -Wextra -pthread thread_create.c -o thread_create
运行:
bash
./thread_create
输出的先后顺序可能每次不同,这是正常现象。
8. 示例代码逐段理解
8.1 参数结构体
c
struct worker_arg {
int number;
const char *message;
};
pthread_create 只能传入一个 void * 参数。需要传递多个值时,先把它们放进结构体,再传结构体地址。
8.2 在线程函数中恢复类型
c
struct worker_arg *info = arg;
void * 可以保存任意对象地址,但使用前必须恢复成正确类型。C 语言中从 void * 转换为其他对象指针不要求显式强制转换。
8.3 创建线程
c
pthread_create(&thread1, NULL, worker, &arg1);
含义依次是:
text
把线程 ID 写入 thread1
使用默认线程属性
从 worker 函数开始运行
把 arg1 的地址传给 worker
8.4 等待线程
c
pthread_join(thread1, NULL);
主线程等待 thread1 结束。第二个参数为 NULL,表示不接收线程返回值。
默认创建的线程是 joinable。若既不 pthread_join 也不 pthread_detach,已结束线程的一部分资源不能及时释放。
9. 参数生命周期是初学者最容易犯的错误
9.1 错误:传入循环变量地址
c
for (int i = 0; i < 3; ++i) {
pthread_create(&threads[i], NULL, worker, &i);
}
三个线程拿到的是同一个 i 的地址。线程真正读取时,i 可能已经改变,甚至已经离开作用域。
9.2 正确:每个线程有独立参数
c
int numbers[3] = {0, 1, 2};
for (int i = 0; i < 3; ++i) {
pthread_create(&threads[i], NULL, worker, &numbers[i]);
}
并且主线程必须保证 numbers 在线程读取完成前一直有效,通常在 pthread_join 全部返回后才能离开其作用域。
10. 线程函数怎样返回结果
线程函数可以:
c
return pointer;
这等价于:
c
pthread_exit(pointer);
另一个线程通过 pthread_join 接收:
c
void *result;
pthread_join(thread_id, &result);
不能返回线程函数局部变量的地址:
c
static void *wrong_worker(void *arg)
{
int value = 42;
return &value; /* 错误:线程结束后局部变量失效 */
}
基础练习中可以让调用方提供结果存储,或在线程中动态分配并由接收方释放。当前示例不需要结果,所以直接 return NULL。
11. main 返回为什么会结束所有线程
若主线程执行:
c
return 0;
它等价于结束整个进程,其他线程也会一起终止。主线程通常应该:
pthread_join等待工作线程;或- 在确有需要时调用
pthread_exit(NULL),只结束主线程而保留其他线程。
初学阶段优先使用 pthread_join,生命周期最清楚。
12. 录音中的输入线程与输出线程
录音设计了下面的结构:
#mermaid-svg-cIjkOqwYYtSZBfK8{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-cIjkOqwYYtSZBfK8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cIjkOqwYYtSZBfK8 .error-icon{fill:#552222;}#mermaid-svg-cIjkOqwYYtSZBfK8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cIjkOqwYYtSZBfK8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .marker.cross{stroke:#333333;}#mermaid-svg-cIjkOqwYYtSZBfK8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cIjkOqwYYtSZBfK8 p{margin:0;}#mermaid-svg-cIjkOqwYYtSZBfK8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster-label text{fill:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster-label span{color:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster-label span p{background-color:transparent;}#mermaid-svg-cIjkOqwYYtSZBfK8 .label text,#mermaid-svg-cIjkOqwYYtSZBfK8 span{fill:#333;color:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .node rect,#mermaid-svg-cIjkOqwYYtSZBfK8 .node circle,#mermaid-svg-cIjkOqwYYtSZBfK8 .node ellipse,#mermaid-svg-cIjkOqwYYtSZBfK8 .node polygon,#mermaid-svg-cIjkOqwYYtSZBfK8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .rough-node .label text,#mermaid-svg-cIjkOqwYYtSZBfK8 .node .label text,#mermaid-svg-cIjkOqwYYtSZBfK8 .image-shape .label,#mermaid-svg-cIjkOqwYYtSZBfK8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cIjkOqwYYtSZBfK8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .rough-node .label,#mermaid-svg-cIjkOqwYYtSZBfK8 .node .label,#mermaid-svg-cIjkOqwYYtSZBfK8 .image-shape .label,#mermaid-svg-cIjkOqwYYtSZBfK8 .icon-shape .label{text-align:center;}#mermaid-svg-cIjkOqwYYtSZBfK8 .node.clickable{cursor:pointer;}#mermaid-svg-cIjkOqwYYtSZBfK8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .arrowheadPath{fill:#333333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cIjkOqwYYtSZBfK8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cIjkOqwYYtSZBfK8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cIjkOqwYYtSZBfK8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster text{fill:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 .cluster span{color:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 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-cIjkOqwYYtSZBfK8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cIjkOqwYYtSZBfK8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cIjkOqwYYtSZBfK8 .icon-shape,#mermaid-svg-cIjkOqwYYtSZBfK8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cIjkOqwYYtSZBfK8 .icon-shape p,#mermaid-svg-cIjkOqwYYtSZBfK8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cIjkOqwYYtSZBfK8 .icon-shape .label rect,#mermaid-svg-cIjkOqwYYtSZBfK8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cIjkOqwYYtSZBfK8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cIjkOqwYYtSZBfK8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cIjkOqwYYtSZBfK8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pthread_join
pthread_join
输入线程
读取 stdin
共享环形缓冲区
输出线程
写到 stdout
主线程
它能说明同一进程中有三条执行线:主线程、输入线程和输出线程。但当前版本存在几个问题:
- 输入线程和输出线程同时访问共享缓冲区,没有互斥锁,构成数据竞争;
- 使用字节值
0表示"空位置",无法正确传递真实的零字节; - 缓冲区满与空的判断不完整,快写慢读时可能覆盖未读数据;
- 输出线程用短暂
sleep轮询,只是降低空转,不能保证同步; - 两个线程都是无限循环,主线程会永远阻塞在第一个
pthread_join; calloc返回值必须检查,结束路径还要保证free只执行一次。
因此本节先理解线程创建流程。等学到互斥锁和条件变量后,再把共享缓冲区改造成安全的生产者消费者模型。
13. 共享变量为什么会产生数据竞争
假设两个线程同时执行:
c
counter++;
它并不一定是一条不可分割指令,通常可以理解为:
text
读取 counter → 加 1 → 写回 counter
两个线程可能都读到旧值,然后分别写回相同的新值,导致一次增加丢失。之后需要学习:
pthread_mutex_t互斥锁;pthread_cond_t条件变量;- 原子操作;
- 线程安全的数据结构。
14. 编译参数为什么使用 -pthread
bash
gcc source.c -o program -pthread
-pthread 会为线程程序设置所需的编译和链接选项。只写 -lpthread 主要影响链接阶段,通常不如 -pthread 完整。
15. 常见错误
| 错误 | 结果 | 正确做法 |
|---|---|---|
用 perror 报告 pthread_create 失败 |
可能打印无关的 errno |
保存返回值,用 strerror(error) |
| 假设新线程一定先运行 | 输出顺序不稳定 | 使用同步机制,不依赖调度顺序 |
主线程直接 return |
所有工作线程一起结束 | 先 pthread_join |
| 传入短生命周期局部变量地址 | 线程读取无效或已改变的数据 | 保证参数活到线程使用结束 |
| 返回线程局部变量地址 | 得到悬空指针 | 使用调用方存储或动态内存 |
不 join 也不 detach |
已结束线程资源无法及时释放 | 二选一管理生命周期 |
| 多线程直接修改共同变量 | 数据竞争、结果不确定 | 使用互斥锁等同步机制 |
用 sleep 代替同步 |
偶发错误仍然存在 | 使用锁、条件变量或 join |
16. 教材与录音表述校正
pthread_t在课程系统中可能表现为unsigned long,但标准不保证其底层类型,应当作不透明线程标识。- 线程不仅各自拥有栈,还拥有线程 ID、寄存器状态、信号屏蔽字和独立的
errno等状态。 pthread_create失败时直接返回错误号,通常不设置errno。- 创建成功后,主线程与新线程谁先运行不确定。
- 线程共享地址空间使通信方便,同时引入数据竞争;直接访问共享变量不等于访问安全。
sleep可以减少忙等占用,不能提供数据同步和内存可见性保证。- 默认线程是 joinable,结束后仍需
pthread_join回收,或提前设置为 detached。 - 录音的共享缓冲区演示需要在后续互斥锁与条件变量课程中补全,当前不应作为线程安全代码直接用于项目。
17. 本节最低掌握标准
学完后能够回答:
- 同一进程的线程共享哪些主要资源?
- 每个线程有哪些独立状态?
pthread_create的四个参数分别是什么?- 线程入口函数为什么必须是
void *函数(void *)? - 为什么 Pthreads 错误不能直接使用
perror? - 为什么不能假设主线程或工作线程固定先运行?
- 为什么参数对象必须在线程使用期间一直有效?
- 为什么不能返回线程局部变量地址?
pthread_join有什么作用?- 为什么共享缓冲区需要互斥锁和条件变量?
最低实践要求:
- 创建一个线程并传入一个整数;
- 创建两个线程并分别传入不同结构体;
- 主线程使用
pthread_join等待它们结束; - 能正确使用
strerror打印 Pthreads 错误; - 使用
-pthread编译。
18. PDF 页码与录音时间索引
| 主题 | PDF 页码 | 录音时间 |
|---|---|---|
| 线程概念、共享资源与独立栈 | 124 | 00:10---02:19 |
pthread_create 原型与四个参数 |
124及录音补充 | 02:20---04:25 |
| 创建输入、输出两个线程 | 录音补充,对应后续教材示例 | 04:26---09:30 |
| 共享缓冲区读写逻辑 | 录音补充 | 07:27---11:29 |
pthread_join 与三条执行线 |
录音补充 | 11:30---12:35 |
| 编译运行和结果演示 | 录音补充 | 12:36---13:11 |
进一步核对资料:
- Linux man-pages:pthread_create(3)、pthread_join(3)、pthreads(7)。