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 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码6 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx7 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用7 小时前
Spring基础使用
java·后端·spring
程序员老陆8 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光8 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
Su米苏8 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园8 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁8 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网8 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端