040 线程控制

194 阅读26分钟

线程控制

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.cc
    	g++ -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();         // 获取当前线程 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;
}

运行结果示例:

[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);         // 所有线程都要分配内存,让他们分别乘以 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;
}

运行结果示例:

[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);  // 主动退出,返回值是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;
}

运行结果示例:

[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 加 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. 可重入函数应该是什么样?

// 完全不使用全局变量,只用局部变量
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_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. 多线程的协同

#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_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;
}