当前位置: 首页 > news >正文

040 线程控制

🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

  • 线程控制
    • 1. POSIX 线程库
      • 1. 什么是 POSIX 线程库(pthread)
      • 2. 特点
      • 3. pthread_t
        • 1. 功能
        • 2. 本质
      • 4. pthread_create —— 创建新线程
        • 1. 功能
        • 2. 函数原型
        • 3. 参数详解
        • 4. 返回值
        • 5. 代码示例
        • 6. 线程监控与查看
          • 1. `ps axj | head -1 & ps axj | grep pthread_create | grep -v grep`
          • 2. `ps -aL | head -1 & ps -aL | grep pthread_create | grep -v grep`
          • 3. `ldd pthread_create`
    • 2. 线程的调度
      • 1. 线程调度单位到底是谁?
      • 2. 什么是 LWP(Light Weight Process)?
      • 3. Linux 中 pthread 与 LWP 的关系?
      • 4. 为什么说 “我们以前接触到的都是单线程进程,PID 和 LWP 相等”?
      • 5. pthread_self —— 获取线程 ID
        • 1. 功能
        • 2. 函数原型
        • 3. 返回值
        • 4. 代码示例
    • 3. 线程等待
      • 1. 线程等待是什么?
      • 2. pthread_join —— 阻塞线程
        • 1. 功能
        • 2. 函数原型
        • 3. 参数详解
        • 4. 返回值
        • 5. 代码示例
      • 3. 线程退出值 = 退出码(为啥只能拿这个)?
        • 1. 为什么线程退出时只能“返回一个退出码”?
        • 2. 为什么不是像 `fork()` 那样返回整型或 exit code?
    • 4. 线程终止
      • 1. 线程终止的 4 种方式(核心)
      • 2. `pthread_exit` —— 主动退出当前线程
        • 1. 功能
        • 2. 函数原型
        • 3. 参数详解
        • 4. 返回值
        • 5. 代码示例
      • 3. `pthread_cancel` —— 发送请求
        • 1. 功能
        • 2. 函数原型
        • 3. 参数详解
        • 4. 返回值
        • 5. 注意事项
        • 6. 代码示例
      • 4. 小结
    • 5. 线程分离
      • 1. 线程分离是什么,有什么用?
      • 2. `pthread_detach` 函数
        • 1. 功能
        • 2. 函数原型
        • 3. 参数说明
        • 4. 返回值
        • 5. 代码示例
      • 3. 线程分离的本质是什么?
      • 4. 小结
    • 6. C++ 语言层面上的多线程支持(C++11 起,了解)
      • 1. pthread VS std:: thread
      • 2. `std::thread` vs `pthread` 对照表
      • 3. 代码示例
        • 1. 创建线程
        • 2. 等待线程结束:`join()`
        • 3. 分离线程:`detach()`
        • 4. 获取线程 ID
        • 5. 支持 lambda(超方便!)
        • 6. 支持类成员函数
    • 7. 可重入与线程安全
      • 1. 代码示例:一个不可重入的函数
      • 2. 可重入函数应该是什么样?
      • 3. 小结
    • 8. 代码实战
      • 1. 多种终止方式
      • 2. 多线程的协同
      • 3. 多线程特性综合演示
    • 共勉

线程控制

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 函数即可。

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

4. pthread_create —— 创建新线程

1. 功能

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

2. 函数原型
#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. 代码示例
#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. 编译命令

    g++ -o pthread_create pthread_create.cc -lpthread
    

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

  2. makefile 文件:

    pthread_create:pthread_create.ccg++ -o $@ $^ -std=c++11 -lpthread
    .PHONY: clean
    clean:rm -f pthread_create
    

运行结果示例:

image-20250722153949184

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

6. 线程监控与查看
  1. 查看进程和线程

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

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

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

      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>函数原型:

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. 函数原型
#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. 代码示例
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* func(void* arg)
{pthread_t tid = pthread_self();         // 获取当前线程 IDcout << "子线程 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();    // 主线程自身 IDcout << "主线程 pthread_self() = " << main_tid << endl;printf("这是主线程 pthread_t 变量的地址 %p\n", &main_tid);return 0;
}

运行结果示例:

[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. 函数原型
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
3. 参数详解
参数名类型说明
threadpthread_t要等待的线程 ID,一般是 pthread_create 时返回的
retvalvoid**二级指针,接收线程退出时的退出码信息(可以为 NULL

retval 的注意:

  • 若不关心线程返回什么,可以传 NULL/nullptr
  • 若关心,则要定义 void* result,传 &result,线程退出时返回值会保存在 result 中。
4. 返回值
  • 成功:返回 0。
  • 失败:返回非 0,即错误码(如无效的线程 ID、线程不存在等)。
5. 代码示例
#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);         // 所有线程都要分配内存,让他们分别乘以 10cout << "子线程 " << 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;
}

运行结果示例:

[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. 为什么线程退出时只能“返回一个退出码”?

因为线程函数的原型是:

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. 函数原型
#include <pthread.h>
void pthread_exit(void* retval);
3. 参数详解

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

4. 返回值

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

5. 代码示例
#include <iostream>
#include <pthread.h>
using namespace std;void* myThread(void* arg)
{cout << "子线程正在运行..." << endl;pthread_exit((void*)1234);  // 主动退出,返回值是1234cout << "子线程退出已经退出了,并且这一句话不会被执行!" << 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;
}

运行结果示例:

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

3. pthread_cancel —— 发送请求

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(宏)

#define PTHREAD_CANCELED ((void *) -1)
  • 它是一个 void* 类型的宏常量;
  • 实际上是 (void*)-1,特殊指针,用作标记;
  • 不能拿它当做真实的返回值内容使用,只能 判断它是不是等于某线程的退出码
#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;
}
[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. 代码示例
#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;
}

输出:

[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. 函数原型
#include <pthread.h>
int pthread_detach(pthread_t thread);
3. 参数说明

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

4. 返回值
  • 0:成功。
  • EINVAL:线程不是 joinable 或状态无效。
  • ESRCH:指定的线程 ID 不存在。
5. 代码示例
#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;
}

运行结果示例:

[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

概念说明
pthreadPOSIX 标准定义的 C 语言线程 API,不是 C++ 的一部分。在 Linux 上通过 <pthread.h> 提供。
std::threadC++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 -lpthreadg++ -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. 创建线程
thread t(函数名, 参数...);

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

void func(int& x) { x = 100; }
int val = 0;
thread t(func, ref(val));  // 传引用
t.join();
cout << val;  // 输出 100
2. 等待线程结束:join()
t.join();  // 必须调用,否则程序会崩溃!

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

3. 分离线程:detach()
t.detach();  // 不等它,让它后台运行
4. 获取线程 ID
cout << "当前线程ID: " << this_thread::get_id() << endl;
cout << "t的线程ID: " << t.get_id() << endl;
5. 支持 lambda(超方便!)
thread t([]{cout << "Lambda 线程运行!" << endl;this_thread::sleep_for(1s);
});
t.join();
6. 支持类成员函数
class Worker
{
public:void work(int n){cout << "Worker 工作 " << n << endl;}
};Worker w;
thread t(&Worker::work, &w, 100);  // &w 是对象地址
t.join();

#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;
}
#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. 代码示例:一个不可重入的函数

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;int counter = 0;                        // 全局变量void* task(void* arg)                   // 所有线程都会调用这个函数
{// 每个线程都对同一个 counter 加 1for (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. 可重入函数应该是什么样?

// 完全不使用全局变量,只用局部变量
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. 多种终止方式

#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_exitpthread_exit((void*)2);                                  // 返回退出值 2
}void* thread3(void*)
{cout << "[线程3] 进入无限循环,等待被取消..." << endl;     // 方法3:无限循环,等待 cancelwhile (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;// 取消线程3sleep(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. 多线程的协同

#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. 多线程特性综合演示

#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_treturn 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());                // 立即将当前线程设置为分离状态,这样线程结束后会自动释放资源,无需主线程jointhreadData* td = static_cast<threadData*>(args);// 获取当前线程ID和进程IDstring tid_str = toHex(pthread_self());        // 当前线程的IDint 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);                        // 保存线程IDusleep(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;
}

共勉

在这里插入图片描述
在这里插入图片描述

http://www.dtcms.com/a/398122.html

相关文章:

  • 前端开发利器:nvm、npm与pnpm全面解析与TypeScript/JavaScript选择指南
  • 电影网站如何做seo哪家网站建设公司好
  • LeetCode 刷题【90. 子集 II】
  • Spring Boot启动报错:Failed to configure a DataSource 全面解析与解决方案
  • MongoDB源码delete分析观察者getOpObserver()->onDelete
  • 企业网站模板htmlwordpress cos 配置
  • ACL 2025 Time-LlaMA 大语言模型高效适配时间序列预测
  • 2025开发者云服务器评测:AWS, Vercel, Railway该如何选?
  • 金融数据库--下载全市场股票日线行情数据
  • HTML `<meter>` 标签:原生度量衡指示器,直观展示百分比、评分等量化数据
  • 平安养老险广西分公司 | 开展金融知识公益宣教活动
  • 威海北京网站建设怎么做网站推广世界杯
  • php的网站模板下载如何修改自己的网站标题
  • VS Code 格式化配置优先级与作用机制(不含ESlint)
  • python+springboot+uniapp微信小程序“美好食荐”系统 美食推荐 菜谱展示 用户互动 评论收藏系统
  • 微信小程序页面滚动到指定位置
  • 抢占2025SEO先机:九大趋势洞察与实战行动路线图
  • Ubuntu 安装 Maven 私服 Nexus
  • maven install和package 有什么区别
  • 关于maven编译没把resources资源包含进target目录
  • 网站开发文档合同wap712c
  • [Maven 基础课程]11_Windows 安装 Maven 私服 Nexus
  • LinuxC++项目开发日志——基于正倒排索引的boost搜索引擎(3——通过cppjieba库建立索引模块)
  • 早报库|深圳奔向全球“消费级3D打印第一城”;苹果持续扩招增材制造人才;乌军前线大量使用3D打印地雷
  • 爬虫数据存储:MongoDB 在电商采集中的应用
  • 【STM32项目开源】STM32单片机厨房安全监测系统系统
  • 在 ARM64 Ubuntu 20.04 上部署 Mailu 邮件服务器:深度排查 Docker Bridge 网络通信失败问题
  • ubuntu 20 安装python
  • Golang语言基础篇003_数组、切片、map详解
  • 传统网站开发下载 wordpress语言包