C++多线程,并发

detached线程是什么

Detached线程(分离线程)是C++多线程编程中的一个重要概念,指的是一个与创建它的线程"分离"的线程,其生命周期不再受创建者控制。

核心特征 :当一个线程被设置为 detached 状态后:

  • 资源自动回收 :线程执行完毕后,系统会自动回收其资源(如栈空间),无需其他线程调用 join() 来等待它结束。
  • 无法再被join :一旦分离,就不能再对该线程调用 join(),否则会抛出 std::logic_error 异常。
  • 独立生命周期:即使创建它的主线程已经结束,detached线程仍会继续运行,直到它自己的任务完成。

如何创建detached线程

在C++中,通过 std::thread 对象的 detach() 方法来实现:

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

void background_task() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Detached thread finished its work." << std::endl;
}

int main() {
    std::thread t(background_task);
    t.detach();  // 将线程分离

    std::cout << "Main thread continues..." << std::endl;
    // 主线程结束,但detached线程仍在后台运行
    return 0;
}

Detached线程常用于以下情况:

场景 说明
后台服务任务 如日志记录、定期数据备份、心跳检测等,不需要主线程等待其完成
长期运行的任务 需要独立于主线程持续运行的工作
"即发即忘"的任务 不需要获取返回值或关心执行结果的任务

注意事项与风险

  1. 访问已销毁的变量 :如果detached线程持有指向主线程局部变量的指针或引用,而主线程先结束,这些变量会被销毁,导致detached线程访问野指针------这是未定义行为

  2. 难以调试:detached线程的生命周期不明确,出现问题时难以追踪。

  3. 资源泄漏风险:如果detached线程本身陷入死循环或长时间阻塞,其资源无法被回收。

最佳实践

  • 在调用 detach() 之前,确保线程函数不访问主线程的局部变量(可通过值传递或使用智能指针解决)。
  • 对于需要获取结果的任务,优先使用 std::async 配合 std::future,而不是手动管理detached线程。
  • 分离前检查线程是否可分离:if (t.joinable()) t.detach();

一句话总结:Detached线程就像放飞的鸟------它独立运行,系统会自动照顾它的后事,但你再也无法控制它,也无法知道它飞到了哪里。

atomic?怎么用

std::atomic 是 C++11 引入的原子操作库,用于在多线程环境中安全地操作共享变量,无需使用互斥锁(mutex),性能更高。

cpp 复制代码
#include <atomic>

定义原子变量

cpp 复制代码
std::atomic<int> counter(0);        // 原子 int,初始值 0
std::atomic<bool> flag(false);      // 原子 bool
std::atomic<long long> big(0);      // 原子长整型
std::atomic<int*> ptr(nullptr);     // 原子指针

核心操作

操作 函数 说明
load() 原子读取当前值
store(val) 原子写入新值
交换 exchange(val) 设置新值,返回旧值
CAS compare_exchange_weak/strong(expected, desired) 比较并交换
自增 fetch_add(n)++ 原子加
自减 fetch_sub(n)-- 原子减
cpp 复制代码
std::atomic<int> a(0);

a.store(10);                    // 写入 10
int val = a.load();             // 读取,val = 10
int old = a.exchange(20);       // 设置 20,返回旧值 10
a++;                            // 自增,a = 21
a.fetch_add(5);                 // 加 5,a = 26
cpp 复制代码
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

std::atomic<int> counter(0);

void increment(int n) {
    for (int i = 0; i < n; ++i) {
        counter++;  // 原子操作,线程安全
    }
}

int main() {
    const int num_threads = 4;
    const int increments_per_thread = 100000;
    
    std::vector<std::thread> threads;
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(increment, increments_per_thread);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "最终结果: " << counter.load() << std::endl;
    // 输出: 400000
    return 0;
}

如果用普通 int,结果会小于 400000(数据竞争);用 std::atomic 保证结果正确。

内存序(Memory Order)

原子操作默认使用最严格的 memory_order_seq_cst(顺序一致性),性能稍低。可根据场景选择更宽松的内存序:

内存序 说明 适用场景
relaxed 仅保证原子性,不约束指令重排 计数器、统计
acquire 后续读写不会重排到它前面 读锁
release 前面的读写不会重排到它后面 写锁
acq_rel acquire + release 读-改-写操作
seq_cst 全局顺序一致(默认) 通用场景
cpp 复制代码
counter.fetch_add(1, std::memory_order_relaxed);  // 宽松模式,性能更好

注意事项

注意点 说明
不可拷贝 std::atomic 禁用拷贝构造和赋值
不支持浮点 std::atomic<float> 标准未定义,需用整型模拟
CAS 伪失败 compare_exchange_weak 可能虚假失败,需循环重试
不是万能 只保证单个操作原子性,多个原子变量之间仍需额外同步

std::atomic 就是不用加锁的线程安全变量 ,适合计数器、标志位、状态切换等简单场景。用法就是 load() 读、store() 写、fetch_add() 加减,比 mutex 轻量高效。

相关推荐
逆境不可逃6 小时前
Hello-Agents 第二部分-第六章:框架开发实践
java·人工智能·分布式·学习·架构·rabbitmq
计算机安禾6 小时前
【c++面向对象编程】第29篇:定位new(placement new):在指定内存上构造对象
开发语言·c++·算法
计算机安禾6 小时前
【c++面向对象编程】第27篇:空类的大小为什么是1?——C++对象标识的秘密
开发语言·c++·算法
河阿里6 小时前
Python容器:特性、区别和使用场景
开发语言·python
我不是8神6 小时前
面试题:Gorutine泄露的条件有哪些?
java·开发语言
奇树谦6 小时前
QListView和QListWidget区别详细说明
开发语言
爱好物理的一名程序员XiaoK6 小时前
搭建网站时遇到的只显示空白界面
java
郭龙_Jack6 小时前
Java并发包(JUC)深度解析:从LockSupport到云原生演进
开发语言·云原生·java并发编程
AC赳赳老秦6 小时前
OpenClaw与思维导图工具联动:自动生成工作规划脑图、拆解任务节点,适配职场管理
java·大数据·服务器·数据库·python·php·openclaw