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

相关推荐
亦暖筑序4 小时前
Java 8老系统AI Workflow实战:把一次性AI对话升级成可恢复工作流
java·后端
敲代码的彭于晏5 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
plainGeekDev6 小时前
ButterKnife → ViewBinding
android·java·kotlin
像我这样帅的人丶你还1 天前
Java 后端详解(四):分页与搜索
java·javascript·后端
她的男孩1 天前
数据权限为什么不能只靠注解?Forge 的 Mapper 层 SQL 改写源码拆解
java·后端·架构
tntxia1 天前
Mybatis的日志输入
java
亦暖筑序1 天前
Java 8老系统Browser Agent实战:三层拦截把AI操作后台变成可审计流程
java·后端·设计模式
用户298698530141 天前
Java 实现 Word 文档加密与权限解除
java·后端
Yeats_Liao1 天前
14:Servlet中的页面跳转-Java Web
java·后端·架构
未秃头的程序猿1 天前
告别"if-else地狱"!Java 21模式匹配,代码优雅了10倍
java·后端·面试