Linux 线程创建:pthread_create 与基本回收

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;

它等价于结束整个进程,其他线程也会一起终止。主线程通常应该:

  1. pthread_join 等待工作线程;或
  2. 在确有需要时调用 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
主线程

它能说明同一进程中有三条执行线:主线程、输入线程和输出线程。但当前版本存在几个问题:

  1. 输入线程和输出线程同时访问共享缓冲区,没有互斥锁,构成数据竞争;
  2. 使用字节值 0 表示"空位置",无法正确传递真实的零字节;
  3. 缓冲区满与空的判断不完整,快写慢读时可能覆盖未读数据;
  4. 输出线程用短暂 sleep 轮询,只是降低空转,不能保证同步;
  5. 两个线程都是无限循环,主线程会永远阻塞在第一个 pthread_join;
  6. 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. 教材与录音表述校正

  1. pthread_t 在课程系统中可能表现为 unsigned long,但标准不保证其底层类型,应当作不透明线程标识。
  2. 线程不仅各自拥有栈,还拥有线程 ID、寄存器状态、信号屏蔽字和独立的 errno 等状态。
  3. pthread_create 失败时直接返回错误号,通常不设置 errno。
  4. 创建成功后,主线程与新线程谁先运行不确定。
  5. 线程共享地址空间使通信方便,同时引入数据竞争;直接访问共享变量不等于访问安全。
  6. sleep 可以减少忙等占用,不能提供数据同步和内存可见性保证。
  7. 默认线程是 joinable,结束后仍需 pthread_join 回收,或提前设置为 detached。
  8. 录音的共享缓冲区演示需要在后续互斥锁与条件变量课程中补全,当前不应作为线程安全代码直接用于项目。

17. 本节最低掌握标准

学完后能够回答:

  1. 同一进程的线程共享哪些主要资源?
  2. 每个线程有哪些独立状态?
  3. pthread_create 的四个参数分别是什么?
  4. 线程入口函数为什么必须是 void *函数(void *)?
  5. 为什么 Pthreads 错误不能直接使用 perror?
  6. 为什么不能假设主线程或工作线程固定先运行?
  7. 为什么参数对象必须在线程使用期间一直有效?
  8. 为什么不能返回线程局部变量地址?
  9. pthread_join 有什么作用?
  10. 为什么共享缓冲区需要互斥锁和条件变量?

最低实践要求:

  • 创建一个线程并传入一个整数;
  • 创建两个线程并分别传入不同结构体;
  • 主线程使用 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

进一步核对资料:

相关推荐
布吉岛的石头1 小时前
Java 程序员第 49 阶段5:BERT 预训练目标 MLM+NSP 的工程含义
java·人工智能·深度学习·bert·transformer
摇滚侠2 小时前
《On Java 中文版 基础卷》阅读笔记 对象无处不在 03
java·笔记·python
二十雨辰2 小时前
[学成在线]-01项目背景
java
s6516654963 小时前
IDT和GDT是什么
linux
Android系统攻城狮3 小时前
Linux Gstreamer深度解析之gst_audio_decoder_set_estimate_rate调用流程与实战(四十五)
linux·gstreamer音视频·音视频进阶
FED_AF3 小时前
Linux运维“邪修”功法之通配符
linux·运维
箓维3 小时前
线程的概念和理解
linux
弈栈录3 小时前
Java AI 应用的异步化与高并发设计
java·后端·架构
云杂项3 小时前
tmux指南:安装、配置与高效使用(Ubuntu上)
linux·服务器·ubuntu