040 线程控制

线程控制

1. POSIX 线程库

1. 什么是 POSIX 线程库(pthread)

POSIX(Portable Operating System Interface)线程库,又称(简称) pthread(POSIX Threads),是 Unix 系统下的标准化多线程编程接口(IEEE POSIX 标准(IEEE 1003.1c)定义的线程接口)。它提供了一组函数,用于在同一进程内创建、管理和同步多个线程,实现并发和并行处理。

pthread 线程库是应用层的原生线程库: 应用层指的是这个线程库并不是系统接口直接提供的,而是由第三方帮我们提供的。大部分 Linux 系统都会默认带上该线程库(原生的)。与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以 pthread_ 打头的。要使用这些函数库,要通过引入头文件 <pthreaad.h>,链接这些线程函数库时,要使用编译器命令的 -lpthread 选项。

2. 特点

  1. 与操作系统紧密集成,性能开销小。
  2. 接口统一,可移植性好。
  3. 支持线程同步(互斥锁、条件变量)、线程属性设置等丰富功能。
  4. 线程共享同一进程的内存空间(代码段、堆、全局变量等)。
  5. 线程间通信更高效(直接访问共享数据)。
  6. 适用于需要高并发的场景(如服务器、实时处理)。

3. pthread_t

1. 功能

pthread_t 是 pthread 库定义的线程标识类型,类似于进程中的 PID。每个创建的线程都会分配一个唯一的 pthread_t 值,用来引用和管理该线程。

image-20250723152544973

2. 本质

Linux 下,pthread_t 通常以整数或指针的形式存在(与 glibc 实现有关),我们只需将其当作“黑盒”标识符,配合其他 pthread 函数即可。

1
2
#include <pthread.h>      // 声明 pthread_t 类型
pthread_t tid; // 声明一个线程标识符,创建后,tid 中存储的是新线程的 ID

4. pthread_create —— 创建新线程

1. 功能

创建一个新线程,并让它去执行用户定义的函数。

2. 函数原型

1
2
3
4
5
6
7
8
#include <pthread.h>

int pthread_create(
pthread_t *thread, // 输出参数:返回新线程 ID(pthread_t类型)
const pthread_attr_t *attr, // 线程属性,NULL 表示默认
void *(*start_routine)(void *), // 线程入口函数,即线程启动后要执行的函数(必须接受void*参数并返回void*)
void *arg // 传给入口函数 start_routine 的参数,即线程(要执行)函数的参数
);

3. 参数详解

  • thread:指向 pthread_t 的指针,函数返回后通过它获取新线程的 ID;
  • attr:线程属性指针,可设置线程栈大小、分离状态等,通常传 NULL/nullptr
  • start_routine:线程函数指针,必须形如 void* func(void*)
  • arg:传给线程函数的单个参数,可是任意指针(如结构体、基本类型地址),需要在函数内强转回原类型。

4. 返回值

POSIX 线程(pthreads)函数在出错时 不设置全局 errno ,而是 直接通过返回值返回错误码 (成功为 0,失败为非 0 值)。这与传统系统调用(如 open、read)不同,传统调用通常返回-1 并设置 errno。pthreads 这样做是为了避免多线程环境下对全局 errno 的竞争,提升性能和可移植性。尽管每个线程有独立的 errno 以兼容其他使用它的代码,但 建议始终检查 pthreads 函数的返回值来判断错误 ,而不是依赖 errno。

  • 返回 0:创建成功;
  • 返回非 0:错误码,表示创建失败(如资源不足、权限问题等)。

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* func(void* arg)
{
char* str = (char*)arg;
while(1)
{
cout << str << endl;
sleep(1);
}
}

int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, func, (void*)"我是子线程,ID是:123456");

while(1)
{
cout << "我是主线程!" << endl;
sleep(1);
}

return 0;
}

编译(-lpthread):

  1. 编译命令

    1
    g++ -o pthread_create pthread_create.cc -lpthread

    -lpthread:链接 pthread 库,必须加在源文件或对象文件之后。

  2. makefile 文件:

    1
    2
    3
    4
    5
    pthread_create:pthread_create.cc
    g++ -o $@ $^ -std=c++11 -lpthread
    .PHONY: clean
    clean:
    rm -f pthread_create

运行结果示例:

image-20250722153949184

原子性 指一个操作要么 完全执行成功 ,要么 完全没执行 ,在执行过程中 不会被中断或分割原子性 = 不可分割性,一个操作如果是原子的,就 不会被其他线程或中断打断 ,外界看起来就像“瞬间完成”。

6. 线程监控与查看

  1. 查看进程和线程

    • ps -AL:列出所有线程(Lightweight Process,LWP)

      1
      ps -AL | grep pthread_create		# 输出中,LWP 列就是线程 ID
  2. 查看可执行文件依赖

    • ldd:列出可执行文件所依赖的共享库

      1
      ldd pthread_create			# 可以确认是否已正确链接 libpthread.so
  3. 结合 top 或 htop

    • top 中,按 H 可切换到线程视图;
    • 便于实时监控各线程的 CPU/内存占用情况。

image-20250722161316674

以下是命令的逐步解析:

1. ps axj | head -1 & ps axj | grep pthread_create | grep -v grep
  • ps axj :
  • ps 是 Linux 系统中用于查看进程状态的命令。
  • -a:显示所有终端上的进程,包括其他用户的进程。
  • -x:显示没有控制终端的进程。
  • -j:以长格式显示线程信息,包括线程 ID、进程组 ID、会话 ID 等。
  • head -1:只取第一行,通常是表头信息。
  • grep pthread_create:过滤出包含字符串 pthread_create 的行,这些行通常与使用了 pthread_create 函数创建的线程相关。
  • grep -v grep:排除包含 grep 自身的行,避免干扰。
2. ps -aL | head -1 & ps -aL | grep pthread_create | grep -v grep
  • ps -aL
  • -a:显示所有终端上的进程。
  • -L:显示线程信息,类似于 -j,但格式稍有不同。
3. ldd pthread_create
  • ldd:显示指定可执行文件或共享库所依赖的动态链接库。

clone() 是 Linux 提供的底层系统调用,用于创建子进程或线程,是 fork()pthread_create() 的核心实现基础之一。相比 fork(),它更灵活,可以通过传入不同的标志位来控制父子进程(或线程)之间是否共享地址空间、文件描述符、信号处理等资源,从而实现“线程”效果。因为使用较复杂(需要手动分配栈空间等),只做了解,不推荐直接使用。头文件: <sched.h>函数原型:

1
>int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);

2. 线程的调度

1. 线程调度单位到底是谁?

“应用层的线程与内核的 LWP 是一一对应的,调度的是 LWP 而非 PID。”在 Linux 中:调度单位是 LWP(轻量级进程),而不是进程本身(PID)。

可以理解为:

概念 含义
应用层线程 pthread_create 创建的线程
内核 LWP 每个线程在内核中的调度实体
PID 与 LWP 主线程的 LWP ID 与 PID 相等,子线程的 LWP ID 不等于 PID
调度单位 Linux 中调度的是每个线程(LWP),不是进程整体

2. 什么是 LWP(Light Weight Process)?

LWP 是 Linux 内核中的 最小调度单位,本质上就是一个“执行上下文”:包括程序计数器、栈、寄存器、调度信息等。每个 LWP 都有自己的 ID,即在 ps -AL 中看到的 LWP(Thread ID)。它们共享 所属进程的虚拟内存空间、打开的文件、信号处理器等资源。

3. Linux 中 pthread 与 LWP 的关系?

在 Linux 上,每个 pthread 线程 = 一个 LWP

  • pthread_create() 创建的每一个线程,都会在内核中映射成一个 LWP;
  • 所以 Linux 是通过调度多个 LWP 来实现多线程程序并发运行(先描述再组织)。

4. 为什么说 “我们以前接触到的都是单线程进程,PID 和 LWP 相等”?

单线程程序只有一个主线程,所以它只有一个 LWP。而这个 LWP 的 ID(TID)刚好等于进程 ID(PID),getpid() == gettid()(在主线程中成立)。但如果在程序中创建了多个线程(用 pthread),就会发现:

  • getpid()(获取进程 ID)在每个线程中都一样;
  • gettid()(获取线程 ID)每个线程都不同;
  • ps -ALtop -H 会列出多个线程,每个线程一个 TID(内核调度单位);

系统调度的是 LWP(线程),不是进程(PID),这就是为什么:

  • 多线程程序中,真正被调度运行的是每个线程(LWP)
  • 哪些线程先运行,哪些后运行,完全由调度器决定(不是你代码里的顺序)。
  • 每个 LWP 都可能在不同的 CPU 核心上并发运行(多核 CPU)。

5. pthread_self —— 获取线程 ID

1. 功能

获取 当前线程自身的线程 ID(pthread 库中的 ID 类型 pthread_t),用于线程内部识别自身,或与其他线程 ID 进行比较。

2. 函数原型

1
2
#include <pthread.h>
pthread_t pthread_self(void);
  • 无参数,不需要传入任何值,它自动返回当前线程对应的 pthread_t
  • 返回值类型是 pthread_t,表示当前线程的 ID。

3. 返回值

返回 当前线程的 ID(类型为 pthread_t),可以用这个 ID:

  • 打印出来查看当前线程是谁;
  • 与其他 pthread_t 对比,判断是不是同一个线程;注意:pthread_t 是个不透明类型,比较是否相等,应使用 pthread_equal() 函数。
  • 在调试或日志记录中标识线程身份。

4. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* func(void* arg)
{
pthread_t tid = pthread_self(); // 获取当前线程 ID
cout << "子线程 pthread_self() = " << tid << endl;
printf("这是子线程 pthread_t 变量的地址 %p\n", &tid);
return nullptr;
}

int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, func, nullptr);

sleep(1);

pthread_t main_tid = pthread_self(); // 主线程自身 ID
cout << "主线程 pthread_self() = " << main_tid << endl;
printf("这是主线程 pthread_t 变量的地址 %p\n", &main_tid);

return 0;
}

运行结果示例:

1
2
3
4
5
[hcc@hcss-ecs-be68 Threads]$ ./pthread_self 
子线程 pthread_self() = 140119442462464
这是子线程 pthread_t 变量的地址 0x7f7019980ef8
主线程 pthread_self() = 140119459862336
这是主线程 pthread_t 变量的地址 0x7ffebae7f9e0

主线程和子线程的 pthread_self() 返回值不同,说明它们是两个独立的线程。两个 tid 变量虽然名字类似,但位于不同线程的栈上。多线程环境下,函数的局部变量是线程安全的(自动隔离)


3. 线程等待

1. 线程等待是什么?

在多线程程序中,主线程或其他线程可能需要等待某个线程执行完毕后再继续执行。这个等待的过程叫做 线程等待 。类似于进程中的 wait() 系统调用。

2. pthread_join —— 阻塞线程

1. 功能

阻塞当前线程,直到指定的线程结束,并可 获取该线程的返回值(退出码)

  • 常用于 主线程等待子线程完成任务
  • 可以在子线程中 return 或使用 pthread_exit() 返回一个结果;
  • 主线程通过 pthread_join() 把这个返回值拿到。

2. 函数原型

1
2
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

3. 参数详解

参数名 类型 说明
thread pthread_t 要等待的线程 ID,一般是 pthread_create 时返回的
retval void** 二级指针,接收线程退出时的退出码信息(可以为 NULL

retval 的注意:

  • 若不关心线程返回什么,可以传 NULL/nullptr
  • 若关心,则要定义 void* result,传 &result,线程退出时返回值会保存在 result 中。

4. 返回值

  • 成功:返回 0。
  • 失败:返回非 0,即错误码(如无效的线程 ID、线程不存在等)。

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* func(void* arg)
{
int id = *(int*)arg; // 获取线程编号
sleep(id); // 模拟不同耗时
int* result = new int(id * 10); // 所有线程都要分配内存,让他们分别乘以 10

cout << "子线程 " << id << " 完成任务,返回结果 = " << *result << endl;
return (void*)result; // 返回退出码(指针)
}

int main()
{
pthread_t tids[5]; // 线程 ID 数组
int ids[5]; // 用于传参的线程编号数组

for (int i = 0; i < 5; i++) // 创建多个子线程
{
ids[i] = i + 1; // 编号从 1 开始
pthread_create(&tids[i], nullptr, func, &ids[i]);
}

for (int i = 0; i < 5; i++) // 主线程等待所有子线程完成
{
void* retval = nullptr;
pthread_join(tids[i], &retval); // 阻塞等待子线程退出

int* res = (int*)retval; // 转换返回值
cout << "主线程获取到线程 " << (i + 1) << " 的返回结果 = " << *res << endl;
delete res; // 释放返回结果的内存
}

cout << "所有线程任务已完成,主线程退出。" << endl;
return 0;
}

运行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
[hcc@hcss-ecs-be68 pthread_join]$ ./pthread_join 
子线程 1 完成任务,返回结果 = 10
主线程获取到线程 1 的返回结果 = 10
子线程 2 完成任务,返回结果 = 20
主线程获取到线程 2 的返回结果 = 20
子线程 3 完成任务,返回结果 = 30
主线程获取到线程 3 的返回结果 = 30
子线程 4 完成任务,返回结果 = 40
主线程获取到线程 4 的返回结果 = 40
子线程 5 完成任务,返回结果 = 50
主线程获取到线程 5 的返回结果 = 50
所有线程任务已完成,主线程退出。

3. 线程退出值 = 退出码(为啥只能拿这个)?

pthread_join() 是主线程 阻塞等待 子线程完成,并 获取子线程返回值(退出码) 的唯一方式,而这个退出码只能是 void* 类型,因为 POSIX pthread 模型就规定线程函数只能返回一个 void* 指针。

1. 为什么线程退出时只能“返回一个退出码”?

因为线程函数的原型是:

1
void* (*start_routine)(void*)
  • 它只能有一个返回值,类型是 void*,这是 POSIX 标准设计决定的;
  • 无法直接返回多个值,也不能返回栈上对象(因为线程函数退出后栈就销毁了);
  • 所以如果需要返回复杂数据,必须 动态申请内存(如 new)并 return 指针,主线程通过 pthread_join() 接收并自行释放。

2. 为什么不是像 fork() 那样返回整型或 exit code?

  • fork()进程级别,操作系统可以记录 exit status;
  • pthread_exit()return 是线程级别,线程之间共享地址空间,退出值不需要写入操作系统状态;
  • pthread_join() 只关心线程退出时返回的那块 用户级别的数据指针(void*),而不是操作系统的退出码。

4. 线程终止

1. 线程终止的 4 种方式(核心)

在 POSIX pthread 中,线程的终止方式主要有以下几种

  1. 线程函数运行完毕后自动返回: 这是最自然的退出方式,函数体执行到最后,线程就自动退出。

  2. 在函数中调用 pthread_exit() 主动退出: 这种方式适用于希望 中途退出线程,但又希望返回一个退出值的情况。可随时退出线程、能设置退出码、等效于 return

  3. 其他线程调用 pthread_cancel() 强制取消线程: 一种 异步控制 方式。线程不一定立即退出,需要处于“可取消点”(如 sleep、read 等),线程退出码为 PTHREAD_CANCELED ((void*)-1)注意:如果线程没有设置为可取消状态,pthread_cancel() 无效。

  4. 整个进程退出时,所有线程终止:主线程调用 exit()_exit() 或主线程崩溃 导致整个进程终止时,所有线程也会强制结束。粗暴退出会导致线程无法清理资源,常见于崩溃或异常退出。

终止方式 触发者 是否能传退出码 是否立即退出 是否安全
return 本线程自己 ✅ 是 ✅ 是 ✅ 推荐
pthread_exit() 本线程自己 ✅ 是 ✅ 是 ✅ 推荐
pthread_cancel() 其他线程 ✅(默认为 PTHREAD_CANCELED ❌ 依赖可取消点 ⚠️ 谨慎使用
exit() / 崩溃 任意线程 ❌ 无法获取 ✅ 是 ❌ 不推荐

2. pthread_exit —— 主动退出当前线程

1. 功能

主动退出当前线程,并返回一个退出值(给等待该线程的其他线程)。比 return 更灵活,可在任何地方终止线程。

适用于:

  • 线程需要在函数中间提前退出;
  • 想设置返回值供 pthread_join 获取;

2. 函数原型

1
2
#include <pthread.h>
void pthread_exit(void* retval);

3. 参数详解

void* retval 线程的退出码,可传任意指针或整型强转。是 返回给 pthread_join 的退出值,如果线程已分离,该值会被忽略。

4. 返回值

无返回值,调用后线程立即退出,后面的代码不会执行。

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <pthread.h>
using namespace std;

void* myThread(void* arg)
{
cout << "子线程正在运行..." << endl;
pthread_exit((void*)1234); // 主动退出,返回值是1234

cout << "子线程退出已经退出了,并且这一句话不会被执行!" << endl;
}

int main()
{
pthread_t tid;
void* retval;

pthread_create(&tid, nullptr, myThread, nullptr);
pthread_join(tid, &retval); // 获取子线程返回值

cout << "子线程退出码:" << (long long)retval << endl;
// 注意:在64位的Linux中,void*(指针)是 8 字节,所以转成 long long,如果转成 int 会报错!
return 0;
}

运行结果示例:

1
2
3
[hcc@hcss-ecs-be68 pthread_exit]$ ./pthread_exit 
子线程正在运行...
子线程退出码:1234

3. pthread_cancel —— 发送请求

1. 功能

向指定线程发送“取消请求”,尝试强制终止它(但不一定立即终止)。

适用于:

  • 主动终止长时间运行或卡死的线程;
  • 线程之间的控制与协作场景。

2. 函数原型

1
2
#include <pthread.h>
int pthread_cancel(pthread_t thread);

3. 参数详解

pthread_t thread 目标线程的线程 ID。

4. 返回值

返回值 含义
0 成功发送取消请求(注意:不是线程已退出!
ESRCH 没有找到指定的线程 ID(线程不存在)
EINVAL(少见) 线程 ID 无效(某些实现中使用)

5. 注意事项

  1. 不是立即强制终止线程!
    • 被取消线程 必须处于可取消状态,且处于 取消点
    • 常见取消点有 sleepreadpthread_join 等。
  2. 线程取消成功后,其退出值为 (void*)PTHREAD_CANCELED,用来判断线程是否被取消。
  3. 如何查一个函数是不是取消点?
    • 查阅 POSIX 官方文档
    • 使用命令:man 7 pthreads,在 Linux 手册中输入 / 搜索 “Cancellation points”,会列出所有标准取消点函数。

PTHREAD_CANCELED 是 POSIX 线程库(pthread)中一个 宏常量,它 用于判断/标识线程是否是被 pthread_cancel() 强制取消退出的,而不是正常执行完毕返回的,可以在 pthread_join() 后检查返回值是否等于它,来确认线程状态。其本质是个 #define(宏)

1
>#define PTHREAD_CANCELED ((void *) -1)
  • 它是一个 void* 类型的宏常量;
  • 实际上是 (void*)-1,特殊指针,用作标记;
  • 不能拿它当做真实的返回值内容使用,只能 判断它是不是等于某线程的退出码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
>#include <iostream>
>#include <pthread.h>
>#include <unistd.h>
>using namespace std;

>void* func(void* arg)
>{
while (1)
{
cout << "子线程运行中..." << endl;
sleep(1);
}
>}

>int main()
>{
pthread_t tid;
pthread_create(&tid, nullptr, func, nullptr);

sleep(2); // 让子线程跑一会儿

pthread_cancel(tid); // 取消子线程

void* retval;
pthread_join(tid, &retval); // 等待子线程结束

// 检查返回值是否为被取消
if (retval == PTHREAD_CANCELED)
{
cout << "子线程被成功取消,返回值 = PTHREAD_CANCELED" << endl;
}
else
{
cout << "子线程正常退出,返回值 = " << retval << endl;
}

return 0;
>}
1
2
3
4
5
>[hcc@hcss-ecs-be68 pthread_cancel]$ g++ -o PTHREAD_CANCELED PTHREAD_CANCELED.cc -std=c++11 -lpthread
>[hcc@hcss-ecs-be68 pthread_cancel]$ ./PTHREAD_CANCELED
>子线程运行中...
>子线程运行中...
>子线程被成功取消,返回值 = PTHREAD_CANCELED

6. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* func(void* arg)
{
while (true)
{
cout << "子线程工作中..." << endl;
sleep(1); // sleep 是一个可取消点
// pthread_testcancel(); // 手动设置一个取消点
// 注意:如果使用 pthread_testcancel(),在这个程序中会无限打印 "子线程工作中...",在3秒后会自动退出,
// 和sleep(1)一样的效果。虽然看起来像是死循环,但实际上是可以取消的!
}

return nullptr;
}

int main()
{
pthread_t tid;
void* retval;

pthread_create(&tid, nullptr, func, nullptr);
sleep(3); // 让子线程运行一会

pthread_cancel(tid); // 发送取消请求
pthread_join(tid, &retval); // 等待子线程结束并获取退出码

if (retval == PTHREAD_CANCELED)
{
cout << "收到取消请求,子线程被取消了!" << endl;
}

return 0;
}

输出:

1
2
3
4
5
[hcc@hcss-ecs-be68 pthread_cancel]$ ./pthread_cancel 
子线程工作中...
子线程工作中...
子线程工作中...
收到取消请求,子线程被取消了!

4. 小结

函数 主体是谁调用 用于哪个线程 是否立即终止 是否能设置退出码
pthread_exit 当前线程 自己 立即
pthread_cancel 外部线程 目标线程 非立即 返回值为宏

线程分离(Detached Thread)是 Linux 多线程(POSIX 线程 pthread)编程中的一个重要概念。下面我们从概念入手,逐步深入 pthread_detach 函数及其背后机制,讲清楚线程分离的本质。


5. 线程分离

1. 线程分离是什么,有什么用?

在默认情况下(joinable 模式),线程执行完后不会立即释放资源,需要其他线程调用 pthread_join() 与之回收,才能释放其占用的资源(如线程栈、PCB 结构等)。

线程分离(detached) 就是让线程在执行完毕后自动释放自己的资源,不再需要其他线程去 pthread_join() 它,防止资源泄漏。注意:线程一旦结束,系统自动回收资源,不能再被 pthread_join

2. pthread_detach 函数

1. 功能

将一个线程设置为 分离状态,使其结束时资源自动释放。

2. 函数原型

1
2
#include <pthread.h>
int pthread_detach(pthread_t thread);

3. 参数说明

thread:需要设置为分离状态的线程 ID(pthread_t 类型)。

4. 返回值

  • 0:成功。
  • EINVAL:线程不是 joinable 或状态无效。
  • ESRCH:指定的线程 ID 不存在。

5. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <pthread.h>
#include <iostream>
#include <unistd.h>
using namespace std;

void* func(void* arg)
{
sleep(1);
cout << "子线程开始执行..." << endl;
cout << "子线程执行完毕,自动释放资源。" << endl;
pthread_exit(nullptr);
}

int main()
{
pthread_t tid;

// 创建线程(默认是 joinable 状态)
if (pthread_create(&tid, nullptr, func, nullptr) != 0)
{
perror("线程创建失败");
return 1;
}

// 设置线程为分离状态
if (pthread_detach(tid) != 0)
{
perror("线程分离失败");
return 1;
}

cout << "主线程不等待子线程,直接结束。" << endl;

sleep(3);
return 0;
}

运行结果示例:

1
2
3
4
[hcc@hcss-ecs-be68 pthread_detach]$ ./pthread_detach 
主线程不等待子线程,直接结束。
子线程开始执行...
子线程执行完毕,自动释放资源。

3. 线程分离的本质是什么?

线程分离的本质就是线程的一个属性,叫作:

  • PTHREAD_CREATE_JOINABLE(默认)
  • PTHREAD_CREATE_DETACHED(分离)

这个属性决定了线程的生命周期如何管理资源:

  • JOINABLE 状态:线程执行完还要别人 join() 回收资源;
  • DETACHED 状态:线程执行完直接自己清理干净,别人不能 join() 它。

一旦线程设置为分离状态,无法再对它调用 pthread_join(),否则会导致未定义行为,分离线程是否继续执行不取决于主线程是否退出,而取决于进程是否还活着。(分离线程 = 自动回收,非分离线程 = 手动回收)。


4. 小结

  • 如果创建的线程是 短生命周期、且不需要结果回传的(如后台异步日志写入),建议使用 pthread_detach() 或属性设置为分离。
  • 如果需要线程返回值(如子线程计算结果后回传主线程),则必须使用 joinable 并调用 pthread_join()
  • 线程分离后,即使主线程退出,分离线程也不一定会结束,只有当进程退出才会影响!
  • 千万不要创建完线程后忘记 join()detach(),否则会发生资源泄漏,尤其在线程频繁创建时。

6. C++ 语言层面上的多线程支持(C++11 起,了解)

pthread 是 C 语言的 POSIX 线程库(第三方),可在 C++ 中直接使用,但并非 C++ 语言原生支持;从 C++11 起,C++ 在语言层面提供了 std::thread 作为标准多线程支持,具有更好的类型安全和跨平台性,底层在 Linux 上通常基于 pthread 实现,但对用户透明。

1. pthread VS std:: thread

概念 说明
pthread POSIX 标准定义的 C 语言线程 API,不是 C++ 的一部分。在 Linux 上通过 <pthread.h> 提供。
std::thread C++11 标准引入的 C++ 原生线程类,属于 C++ 标准库,头文件 <thread>
对比 pthread(C 风格) std::thread(C++11 起标准库)
来源 POSIX API(非 C++ 标准) C++ 标准库(C++11 起)
头文件 <pthread.h> <thread>
依赖 POSIX 系统(Linux/Unix),Windows 原生不支持 跨平台:Windows / Linux / macOS(编译器支持即可)
语言风格 C 风格:函数指针 + void* 参数 C++ 风格:支持 lambda、成员函数、函数对象、模板
类型安全 void* 传参,易出错 模板自动推导,类型安全
封装性 纯函数式调用,无类封装 std::thread 是类,支持 RAII、移动语义
适合谁 系统编程、嵌入式、高性能定制、底层原理 应用开发、跨平台项目、现代 C++ 开发
退出机制 pthread_exit()pthread_join() t.join()t.detach(),析构时自动检查
可移植性 仅限 POSIX 系统 只要编译器支持 C++11 就可移植
底层实现 直接调用内核 LWP(轻量级进程) 在 Linux 上通常基于 pthread 封装(但对用户透明)
编译选项 g++ -o app app.cpp -lpthread g++ -o app app.cpp -std=c++11(自动链接)

2. std::thread vs pthread 对照表

pthread 函数 std::thread 写法 说明
pthread_create(&tid, nullptr, func, arg) std::thread t(func, arg); 创建线程
pthread_join(tid, &ret) t.join(); 等待线程结束
pthread_exit() return; 线程函数返回即退出
pthread_self() std::this_thread::get_id() 获取当前线程 ID
sleep(1) std::this_thread::sleep_for(1s); 睡眠 1 秒

3. 代码示例

1. 创建线程

1
thread t(函数名, 参数...);

参数会自动拷贝(如果是对象)想传引用?用 std::ref(变量) 包一层。

1
2
3
4
5
void func(int& x) { x = 100; }
int val = 0;
thread t(func, ref(val)); // 传引用
t.join();
cout << val; // 输出 100

2. 等待线程结束:join()

1
t.join();  // 必须调用,否则程序会崩溃!

类比 pthread_join,一个 thread 对象只能 join() 一次。

3. 分离线程:detach()

1
t.detach();  // 不等它,让它后台运行

4. 获取线程 ID

1
2
cout << "当前线程ID: " << this_thread::get_id() << endl;
cout << "t的线程ID: " << t.get_id() << endl;

5. 支持 lambda(超方便!)

1
2
3
4
5
thread t([]{
cout << "Lambda 线程运行!" << endl;
this_thread::sleep_for(1s);
});
t.join();

6. 支持类成员函数

1
2
3
4
5
6
7
8
9
10
11
12
class Worker
{
public:
void work(int n)
{
cout << "Worker 工作 " << n << endl;
}
};

Worker w;
thread t(&Worker::work, &w, 100); // &w 是对象地址
t.join();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <thread>
#include <iostream>
using namespace std;

// 普通函数
void func(string msg)
{
cout << "主线程: " << msg << endl;
}

// 成员函数
class work
{
public:
void run(int x)
{
cout << "子线程: " << x << endl;
}
};

int main()
{
thread t1(func, "我是普通函数线程"); // 1. 普通函数线程
work w;
thread t2(&work::run, &w, 42); // 2. 成员函数线程
thread t3([]() { // 3. lambda线程
cout << "lambda线程: " << endl;
});

t1.join();
t2.join();
t3.join();

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <thread>
#include <chrono> // C++ 中专门处理时间的库

using namespace std;
using namespace chrono;

void threadFunc(const string& name)
{
for (int i = 0; i < 3; ++i)
{
cout << name << ": 工作中 " << i << endl;
this_thread::sleep_for(1s); // 睡1秒
}
}

int main()
{
thread t(threadFunc, "子线程"); // 创建线程(pthread_create)

for (int i = 0; i < 2; ++i) // 主线程也干点事
{
cout << "主线程: 主线程工作 " << i << endl;
this_thread::sleep_for(500ms);
}

t.join(); // 等待子线程结束(类比 pthread_join)

cout << "所有线程结束!" << endl;
return 0;
}

//运行结果示例:
//主线程:主线程工作 0
//子线程:工作中 0
//主线程:主线程工作 1
//子线程:工作中 1
//子线程:工作中 2
//所有线程结束!!

7. 可重入与线程安全

“可重入”指的是一个函数可以被多个线程同时调用,并且不会互相影响,不会出现混乱或崩溃。

1. 代码示例:一个不可重入的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

int counter = 0; // 全局变量

void* task(void* arg) // 所有线程都会调用这个函数
{
// 每个线程都对同一个 counter 加 1
for (int i = 0; i < 5; ++i)
{
++counter;
cout << "线程 " << pthread_self() << " counter = " << counter << endl;
sleep(1);
}

pthread_exit(nullptr);
}

int main()
{
pthread_t t1, t2;

pthread_create(&t1, nullptr, task, nullptr);
pthread_create(&t2, nullptr, task, nullptr);

pthread_join(t1, nullptr);
pthread_join(t2, nullptr);

cout << "最终counter = " << counter << endl;
return 0;
}

这个函数是 不是可重入的,并且运行输出是错乱的,因为 counter全局变量,多个线程同时改它,结果错乱。cout 也是 共享资源,多个线程同时输出可能出现换行错乱。

2. 可重入函数应该是什么样?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 完全不使用全局变量,只用局部变量
void* safe_task(void* arg)
{
int local_counter = 0; // 每个线程有自己的变量
for (int i = 0; i < 5; ++i)
{
++local_counter;
cout << "线程 " << pthread_self() << " local_counter = " << local_counter << endl;
sleep(1);
}

pthread_exit(nullptr);
}

这个 safe_task() 就是 可重入的函数,每个线程都自己玩自己的变量,互不干扰。


3. 小结

现在用线程,只需要记住:可重入函数不使用全局变量,也不操作共享资源,就不会线程混乱。目前只需要做到:尽量只用局部变量,一个线程干自己的事,不要访问别人家的变量,就能避免 90% 的线程问题!

要注意的点 是否说明可重入 建议做法
用全局变量 ❌ 否 每个线程用自己的局部变量
打印输出 cout/printf ❌ 否 少用或后续使用加锁保护输出
多个线程同时调函数 ✅ 是 放心大胆用
pthread API ✅ 无影响 这些 pthread 函数本身是线程安全的

8. 代码实战

1. 多种终止方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
using namespace std;

void* thread1(void*)
{
cout << "[线程1] 正常 return 退出" << endl; // 方法1:函数 return 自然返回
return (void*)1; // 返回退出值 1
// return nullptr;
}

void* thread2(void*)
{
cout << "[线程2] 使用 pthread_exit 主动退出" << endl; // 方法2:显式调用 pthread_exit
pthread_exit((void*)2); // 返回退出值 2
}

void* thread3(void*)
{
cout << "[线程3] 进入无限循环,等待被取消..." << endl; // 方法3:无限循环,等待 cancel
while (true)
{
sleep(1); // 可取消点
}
return nullptr;
}

void* thread4(void*) // 方法4:使用 exit 退出(注意:此会导致整个进程退出!不推荐!)
{
cout << "[线程4] 调用了 exit(3),整个进程都会终止!" << endl;
exit(3); // 会结束整个程序(不推荐在线程中用)
}

int main()
{
pthread_t t1, t2, t3, t4;
void* ret;

// 创建四个线程
pthread_create(&t1, nullptr, thread1, nullptr);
pthread_create(&t2, nullptr, thread2, nullptr);
pthread_create(&t3, nullptr, thread3, nullptr);
// pthread_create(&t4, nullptr, thread4, nullptr);

// 等待线程1退出(正常 return)
pthread_join(t1, &ret);
cout << "[主线程] 线程1退出,退出值 = " << (long long)ret << "(return 返回)" << endl;

// 等待线程2退出(pthread_exit)
pthread_join(t2, &ret);
cout << "[主线程] 线程2退出,退出值 = " << (long long)ret << "(pthread_exit 返回)" << endl;

// 取消线程3
sleep(2); // 等它跑一下
pthread_cancel(t3);
pthread_join(t3, &ret);
if (ret == PTHREAD_CANCELED)
{
cout << "[主线程] 线程3被取消(pthread_cancel)" << endl;
}
else
{
cout << "[主线程] 线程3退出值 = " << (long long)ret << endl;
}

sleep(2); // 等它跑一下
// 等待线程4退出(exit)=> 会导致其他线程无法正常退出,整个进程终止!
// pthread_join(t4, &ret);
// cout << "[主线程] 线程4退出,退出值 = " << (long long)ret << "(exit 终止)" << endl;

return 0;
}

2. 多线程的协同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
using namespace std;

// 枚举线程运行状态
enum Status
{
OK = 0, // 正常
ERROR // 异常
};

class ThreadTask
{
public:
// 构造函数:传入线程名字、起始数、结束数、线程编号
ThreadTask(const string& name, int begin, int end, int id)
: _name(name), _begin(begin), _end(end), _id(id),
_result(0), _status(Status::OK)
{}

~ThreadTask() {} // 析构函数

// 线程的实际任务函数
void run()
{
for (int i = _begin; i <= _end; ++i)
{
_result += i; // 计算指定区间的和
}

printf("线程[%s]运行完毕,计算[%d~%d]结束\n", _name.c_str(), _begin, _end);

// pthread_exit:线程结束时返回一个指针(被主线程pthread_join获取)
pthread_exit(this);
}
public:
string _name; // 线程名称
int _begin; // 区间起点
int _end; // 区间终点
int _id; // 线程编号
int _result; // 计算结果
Status _status; // 线程状态
};

// 线程入口函数(全局函数 / 静态函数)
void* threadEntry(void* arg)
{
// static_cast<T*> 是 C++ 的类型转换运算符:用于在编译时进行类型安全的指针转换
ThreadTask* task = static_cast<ThreadTask*>(arg);

task->run(); // 执行实际任务(内部调用 pthread_exit 退出线程)
return nullptr; // 实际不会执行到这里,因为 run() 中直接退出了线程
}

int main()
{
const int THREAD_NUM = 4; // 总线程数量
pthread_t tids[THREAD_NUM]; // 线程ID数组
ThreadTask* tasks[THREAD_NUM]; // 每个线程绑定一个任务对象

int range = 100; // 每个线程负责计算100个数的和
int start = 1; // 区间起始点

for (int i = 0; i < THREAD_NUM; ++i) // 创建线程
{
string name = "Thread-" + to_string(i + 1);
int end = start + range - 1;

tasks[i] = new ThreadTask(name, start, end, i + 1); // 创建一个任务对象

// 创建线程,执行 threadEntry 函数,参数传入任务对象指针
pthread_create(&tids[i], nullptr, threadEntry, tasks[i]);

start = end + 1;
sleep(1); // 保证顺序输出(演示效果)
}

for (int i = 0; i < THREAD_NUM; ++i) // 等待线程结束 + 收集结果
{
void* ret = nullptr;

pthread_join(tids[i], &ret); // pthread_join 等待线程退出,并获取其返回值

ThreadTask* task = static_cast<ThreadTask*>(ret);

if (task->_status == Status::OK)
{
printf("%s计算[%d~%d]结果: %d\n", task->_name.c_str(), task->_begin, task->_end, task->_result);
}
else
{
printf("%s执行失败!\n", task->_name.c_str());
}

delete task; // 释放任务对象资源
}

cout << "所有线程协作完成!" << endl;
return 0;
}

3. 多线程特性综合演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <string>
using namespace std;

#define NUM 3

// __thread 是 GCC 提供的线程局部存储关键字,用于声明在函数内的变量,每个线程都有自己的副本,互不干扰。
__thread unsigned int thread_local_number = 0; // 每个线程独有
__thread int thread_local_pid = 0; // 每个线程独有

struct threadData // 线程数据结构
{
string threadname; // 线程名称
int thread_id; // 线程编号
};

string toHex(pthread_t tid) // 将pthread_t转换为十六进制字符串(便于显示)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "0x%lx", tid);// 使用%lx格式化pthread_t
return buffer;
}

void InitThreadData(threadData* td, int number) // 初始化线程数据
{
td->threadname = "thread-" + to_string(number);// 设置线程名称
td->thread_id = number; // 设置线程ID
}

void* threadRoutine(void* args) // 线程执行函数 - 所有线程都执行这个函数,但每个线程的数据是独立的
{
pthread_detach(pthread_self()); // 立即将当前线程设置为分离状态,这样线程结束后会自动释放资源,无需主线程join

threadData* td = static_cast<threadData*>(args);

// 获取当前线程ID和进程ID
string tid_str = toHex(pthread_self()); // 当前线程的ID
int process_pid = getpid(); // 进程ID(所有线程共享)

// 初始化线程局部变量
thread_local_number = td->thread_id * 100; // 每个线程有不同的值
thread_local_pid = process_pid; // 每个线程都有自己的副本

cout << "[" << td->threadname << "] 启动!"
<< "线程ID: " << tid_str
<< ", 进程ID: " << process_pid
<< ", 线程局部变量 number: " << thread_local_number << endl;

for (int i = 0; i < 5; i++) // 减少循环次数便于观察
{
// 展示线程的关键特性:
// 1. 同一进程内的所有线程共享进程ID
// 2. 每个线程有自己的线程ID
// 3. 每个线程有自己的线程局部存储变量
cout << "[" << td->threadname << "] " << "第" << (i + 1) << "次执行:"
<< "线程ID: " << tid_str
<< ", 进程ID: " << process_pid
<< ", 局部number: " << thread_local_number
<< ", 局部pid: " << thread_local_pid << endl;

sleep(1); // 休眠1秒
thread_local_number++; // 修改线程局部变量(只影响当前线程)
}

cout << "[" << td->threadname << "] 执行完毕!" << endl;

delete td; // 清理分配的内存
return nullptr; // 返回nullptr表示线程正常结束
}

int main()
{
cout << "主线程ID: " << toHex(pthread_self()) << endl;
cout << "进程ID: " << getpid() << endl << endl;

vector<pthread_t> tids; // 存储所有线程ID的容器

cout << "开始创建 " << NUM << " 个线程..." << endl;

for (int i = 0; i < NUM; i++) // 创建多个线程
{
pthread_t tid; // 线程ID变量
threadData* td = new threadData; // 为每个线程分配独立的数据
InitThreadData(td, i); // 初始化线程数据

int ret = pthread_create(&tid, nullptr, threadRoutine, td);
if (ret != 0)
{
cerr << "创建线程失败: " << strerror(ret) << endl;
delete td; // 创建失败时释放内存
continue;
}

cout << "成功创建线程 " << td->threadname << " (ID: " << toHex(tid) << ")" << endl;

tids.push_back(tid); // 保存线程ID
usleep(100000); // 延时0.1秒,确保线程正确启动
}

cout << "\n所有线程创建完成,主线程等待..." << endl;
sleep(2); // 给线程一些执行时间

cout << "\n=== 尝试对分离线程进行join操作 ===" << endl; // 尝试对分离线程进行join操作(这会失败)
for (size_t i = 0; i < tids.size(); i++)
{
int result = pthread_join(tids[i], nullptr);
printf("对线程 0x%lx 执行join的结果: %d (%s)\n", tids[i], result, strerror(result));
// 由于线程已被分离,这里会返回EINVAL(无效参数)
}

cout << "1. 线程共享进程资源(如进程ID、文件描述符等)" << endl;
cout << "2. 每个线程有自己的线程ID和栈空间" << endl;
cout << "3. __thread关键字创建线程局部存储变量" << endl;
cout << "4. 分离线程结束后自动释放资源" << endl;
cout << "5. 分离线程不能被pthread_join回收" << endl;
cout << "6. 线程执行是并发的,输出顺序不同" << endl;

cout << "\n主线程继续执行中..." << endl;
sleep(5);

cout << "主线程执行完毕,程序退出!" << endl;

return 0;
}