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 轻量高效。

相关推荐
dogstarhuang4 小时前
从 0 到 1 搭建可收费的 API 开放平台(实战)
java·架构·api
VortMall4 小时前
『平台去经营化』平台治理能力全新重构|VortMall微服务商城系统v1.3.10
java·大数据·微服务·商城系统·开源商城·vortmall·去经营化
Echo缘4 小时前
嵌入式系统C语言资源分类与内存分布分析
c语言·开发语言
吴声子夜歌4 小时前
Redis 3.x——集群故障转移
java·数据库·redis·集群
Java面试题总结5 小时前
mybatis插件
java·tomcat·mybatis
JaneConan5 小时前
鸿蒙 ArkUI 深水区:@Watch 和 @Observed,状态变了「自动跑」+ 嵌套对象「深层重绘」
开发语言·后端·ui·harmonyos
赤水无泪5 小时前
qt中图标、名称的设置方式
开发语言·qt
王莎莎-MinerU5 小时前
MCP 解决的是工具接入,科研 Agent 还缺的是科学证据接口标准化
开发语言·网络·人工智能·深度学习·pdf·c#·php
橘子海全栈攻城狮5 小时前
【最新源码】基于SpringBoot + Vue的超市管理系统的设计与实现D002
java·开发语言·vue.js·spring boot·后端·spring
smartvxworks5 小时前
Linux 实时内核(Linux Real-Time Kernel)详解:原理、实践与优化
java·linux·服务器