当前位置: 首页 > article >正文

多线程编程中什么时候使用锁和原子操作

在高并发编程中,锁(std::mutex原子操作(std::atomic 都可以用来保证线程安全,但它们的适用场景不同。选择哪种方式取决于性能、代码复杂度以及对数据一致性的要求。


1. 适用于 std::atomic(无锁编程)的场景

1.1 适用于简单数据类型的并发修改

如果你需要在多个线程间修改简单的数据类型(如 intbool、指针等),并且这些操作可以用单个 CPU 原子指令完成,则 std::atomic 更合适。

适用场景

  • 计数器(如请求数、访问次数)
  • 标志位(如任务完成标志 std::atomic<bool>
  • 指针交换(如 std::atomic<void*> 共享数据指针)

示例:线程安全的计数器

std::atomic<int> counter{0};

void worker() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}
  • fetch_add() 直接使用 CPU 无锁指令,多个线程可以同时执行,无需等待。

1.2 适用于无锁数据结构

如果你在实现 高性能数据结构,如无锁队列(lock-free queue)或无锁栈(lock-free stack),std::atomic 是必要的。

适用场景

  • 无锁队列(Lock-Free Queue)
  • 无锁栈(Lock-Free Stack)
  • 无锁哈希表(Lock-Free HashMap)

示例:无锁栈(Lock-Free Stack)

struct Node {
    int data;
    Node* next;
};

std::atomic<Node*> head{nullptr};

void push(int value) {
    Node* new_node = new Node{value, nullptr};
    do {
        new_node->next = head.load();
    } while (!head.compare_exchange_weak(new_node->next, new_node));
}
  • compare_exchange_weak() 确保 head 在修改前仍然是预期值,如果 head 被其他线程改了,会自动重试。

1.3 适用于低延迟场景

高性能计算实时系统中,std::atomic 能减少线程同步的开销,避免上下文切换

适用场景

  • 实时系统(如音视频处理)
  • 游戏引擎(如帧率控制、输入处理)
  • 高频金融交易(如撮合交易引擎)

示例:多线程计时器

std::atomic<bool> stop_timer{false};

void timer() {
    while (!stop_timer.load()) {
        // 执行定时任务
    }
}
  • std::atomic<bool> 可以被多个线程安全地访问,不会产生锁竞争。

2. 适用于 std::mutex(锁)的场景

尽管原子操作效率更高,但在以下情况下,std::mutex 更适合


2.1 适用于修改复杂数据结构

如果数据结构较复杂,且多个线程需要同时修改多个变量,那么 std::mutex 更安全。

适用场景

  • 修改 std::vectorstd::map 等 STL 容器
  • 修改多个变量
  • 涉及条件变量(std::condition_variable)的情况

不适合用 std::atomic 的例子

struct Data {
    int x;
    int y;
};
std::atomic<Data> data;  // ❌ 错误,不能直接用 std::atomic 处理多个变量

上面代码是错误的,因为 std::atomic 不能保证 xy 在一起更新的原子性。

正确做法:使用 std::mutex

struct Data {
    int x;
    int y;
};
Data data;
std::mutex data_mutex;

void update() {
    std::lock_guard<std::mutex> lock(data_mutex);
    data.x += 1;
    data.y += 1;
}
  • std::mutex 确保 xy 同时更新,防止竞争条件(race condition)。

2.2 适用于写多读少(写多线程竞争激烈)

如果写操作频繁,std::atomic 可能导致**缓存一致性(cache coherence)**问题,而 std::mutex 可以降低竞争。

适用场景

  • 高频写入、低频读取的共享资源
  • 数据库事务
  • 日志系统

示例:日志系统

std::mutex log_mutex;

void log(const std::string& message) {
    std::lock_guard<std::mutex> lock(log_mutex);
    std::cout << message << std::endl;
}
  • std::mutex 确保多个线程不会交错输出日志。

2.3 适用于临界区需要长时间执行

如果代码逻辑较复杂,需要执行多个步骤,使用 std::atomic 不现实,此时 std::mutex 更合适。

适用场景

  • 需要执行多个步骤的临界区
  • 持有锁的时间较长

示例:银行账户

struct Account {
    int balance;
    std::mutex mtx;
};

void transfer(Account& from, Account& to, int amount) {
    std::lock_guard<std::mutex> lock(from.mtx);
    from.balance -= amount;
    
    std::lock_guard<std::mutex> lock2(to.mtx);
    to.balance += amount;
}
  • std::atomic 无法保证多个账户的一致性,必须用 std::mutex 保护。

3. 选择 std::atomic 还是 std::mutex

场景适合 std::atomic适合 std::mutex
共享简单变量(如 intbool
共享指针(如 std::atomic<void*>
复杂数据结构(如 std::vector
需要修改多个变量
高性能低延迟应用(如游戏引擎)
需要等待(如 std::condition_variable
数据库事务、日志系统
读多写少的场景
写多读少的场景

4. 结论

  • std::atomic 适用于无锁编程,适用于高性能、短时锁定的操作(计数器、标志位、指针交换)
  • std::mutex 适用于修改多个变量、复杂数据结构、需要等待的场景
  • 如果可以使用 std::atomic,优先使用 std::atomic,因为它避免了上下文切换,提高了性能
  • 但当 std::atomic 不能满足一致性需求时(如同时修改多个变量),必须使用 std::mutex

所以,在实际应用中,先尝试用 std::atomic,如果无法保证数据完整性,再考虑 std::mutex! 🚀


http://www.kler.cn/a/597863.html

相关文章:

  • C#单例模式
  • Redis集群模式(优缺点)
  • AI重构工程设计、施工、总承包行业:从智能优化到数字孪生的产业革命
  • 群体智能优化算法-蛾火焰优化算法(Moth-Flame Optimization Algorithm,含Matlab源代码)
  • uboot(bootrom的作用)
  • [快乐学坊_2] 后端api测试
  • 数据结构篇——二叉树的存储与遍历
  • UnoCSS极速入门:下一代原子化CSS引擎实战指南
  • CVPR 2025 | 文本和图像引导的高保真3D数字人高效生成GaussianIP
  • Gradle/Maven 本地仓库默认路径迁移 (减少系统磁盘占用)
  • 【中文翻译】第1章-The Algorithmic Foundations of Differential Privacy
  • OTN(Optical Transport Network,光传输网络)
  • 机器人的位姿变换左乘与右乘
  • The First Indoor Pathloss Radio Map Prediction Challenge
  • 数组作为哈希表的妙用:寻找缺失的第一个正数
  • TensorFlow面试题及参考答案
  • uniapp vue3使用uniapp的生命周期
  • 如何高效参与 GitHub 知名项目开发并成为核心贡献者
  • DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例11,TableView15_11带分页的导出表格示例
  • SpringCloud构建一个服务步骤