[C++高频精进] 并发编程:线程基础

核心要点速览

  • 线程 vs 进程:进程是资源分配单位(独立内存),线程是调度单位(共享进程内存),线程通信成本更低
  • 线程创建:std::thread支持函数、Lambda、函数对象三种方式
  • 线程管理:join()等待回收、detach()分离(慎用)、joinable()检查状态
  • 线程标识:std::this_thread::get_id()获取 ID,std::thread::id判断唯一性
  • 线程状态:就绪、运行、阻塞、终止
  • 常用接口: std::this_thread 命名空间提供 sleep_forsleep_untilyield 等工具函数。

一、线程的概念

1. 进程与线程的区别

特性进程 (Process)线程 (Thread)
定义程序的一次执行实例,资源分配的最小单位。进程内的执行单元,调度的最小单位。
资源独立的代码、数据、堆栈空间。共享进程的代码、全局数据,独立的栈和寄存器。
隔离地址空间独立 (高隔离性)。共享进程地址空间 (低隔离性)。
通信进程间通信 (IPC),开销大直接共享数据,开销小
开销创建、切换、销毁开销大创建、切换、销毁开销小 (轻量化)。

2. 线程的优势

  • 并发执行多个任务,提升程序响应速度。
  • 充分利用多核 CPU 资源,提高 CPU 利用率。
  • 比进程更轻量,资源消耗少、调度效率高。

3. 用户线程 vs 内核线程

  • 用户线程:用户空间管理,不依赖内核,创建销毁快,但内核无法感知,调度需用户实现。
  • 内核线程:内核空间管理,内核直接调度,支持真正并行,但创建销毁开销比用户线程高。
  • 常见映射:1:1(内核线程对应用户线程)、M:N(多个用户线程映射到多个内核线程)。

二、线程创建(std::thread)

C++11 std::thread标准化线程操作,跨平台兼容,无需依赖平台 API。

创建方式

  • 函数 / 函数指针:
    void func(int a) { /* 线程逻辑 */ }
    std::thread t(func, 10);
  • Lambda 表达式: 简洁高效,可捕获外部变量(需注意捕获权限与生命周期)。
    std::thread t([]{ 
        std::cout << "Lambda 线程" << std::endl; 
    });
  • 类成员函数: 需传递成员函数指针、对象指针(或引用)及参数。
    class Task {public:
        void run(int a) { /* 线程逻辑 */ }};
    Task task;
    std::thread t(&Task::run, &task, 20); // &task 为对象指针

注意事项

  • 线程创建后需立即管理(join()detach()),否则析构时抛出std::terminate异常。
  • 传递参数时,默认按值拷贝,需传递引用时用std::ref/std::cref(避免拷贝开销或悬垂引用)。

三、线程生命周期与管理

1. 线程状态

  • 就绪:已创建,等待 CPU 调度(具备运行条件)。
  • 运行:占用 CPU,执行线程逻辑。
  • 阻塞:因等待资源(如锁、IO)暂停执行,释放 CPU。
  • 终止:线程执行完毕或被强制终止,资源等待回收。

2. 线程管理函数

(1)join()
  • 功能:主线程阻塞,等待子线程执行完毕后再继续,回收子线程资源(避免 “僵尸线程”)。
  • 限制:一个线程只能调用一次join(),调用后joinable()返回false
(2)detach()
  • 功能:主线程与子线程分离,子线程后台运行,主线程不等待。
  • 风险:子线程依赖的主线程资源(如局部变量)可能提前释放,导致悬垂引用(崩溃风险)。
  • 适用场景:子线程逻辑独立,不依赖主线程局部资源,且无需主线程等待。
(3)joinable()
  • 功能:检查线程是否可join(未调用join()/detach(),且线程未终止)。
  • 用途:避免重复joindetach导致的未定义行为(如join()已调用的线程)。

3. 僵尸线程与孤儿线程

  • 僵尸线程 (Zombie): 子线程已终止,但主线程未调用 join() 回收其进程控制块 (PCB) 资源,导致资源泄漏。

    • 危害: 长期积累会耗尽系统资源。
    • 避免: 必须 join()detach()
  • 孤儿线程 (Orphan): 主线程先于子线程退出,子线程被操作系统托管init 进程 (Linux) 或系统进程 (Windows),由托管进程负责回收。

    • 特点: 不会导致资源泄漏,但其执行逻辑必须是独立的。

4. std::this_thread 常用接口

std::this_thread 命名空间用于操作当前正在执行的线程:

接口功能示例
sleep_for(d)让当前线程休眠指定时长,释放 CPU。sleep_for(chrono::seconds(1));
sleep_until(tp)让当前线程休眠到指定时间点。适用于定时任务。
yield()当前线程主动让出 CPU 给同优先级的就绪线程,自身回到就绪态。适用于提升公平性,避免长时间占用 CPU。
get_id()获取当前线程的唯一 ID。cout << this_thread::get_id();

四、线程标识

  • 线程 IDstd::thread::id类型,每个线程有唯一标识(可通过==/!=判断唯一性)。
  • 获取方式
    • 子线程 ID:std::thread t(func); t.get_id();
    • 当前线程 ID:std::this_thread::get_id();
  • 特殊 ID:默认构造的std::thread::id表示 “无关联线程”(可判断线程是否有效)。
    std::thread::id default_id; // "无关联线程" ID
    if (t.get_id() != default_id) { 
        // 线程 t 是有效线程
    }

五、易错

  1. 未管理std::thread:创建后未调用join()/detach(),析构时抛异常。
  2. detach()后访问主线程局部资源:子线程可能在主线程局部变量销毁后执行,导致悬垂引用。
  3. 重复join():对已join的线程再次调用join(),引发未定义行为(需用joinable()检查)。
  4. 线程参数按值传递:需传递引用时未用std::ref,导致拷贝开销或修改无效。
  5. 线程对象不允许直接赋值或拷贝(编译报错),必须使用 std::move 转移所有权。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值