0

0

如何在C++中使用std::atomic进行原子操作_C++原子操作与无锁编程

尼克

尼克

发布时间:2025-09-22 18:18:01

|

779人浏览过

|

来源于php中文网

原创

原子操作通过互斥访问共享数据实现线程安全,C++中std::atomic提供原子读写能力。其核心操作包括load、store、exchange及compare_exchange_weak/strong,后者常用于无锁算法。示例中多个线程对std::atomic counter进行递增,确保结果正确为40000。内存顺序如memory_order_relaxed至memory_order_seq_cst影响同步强度与性能,需根据需求选择以平衡效率与一致性。自旋锁可用std::atomic实现,通过exchange和store配合acquire-release语义完成。使用陷阱包括伪共享、ABA问题、内存泄漏和死锁,需采用填充、版本号、Hazard Pointer等技术规避。

如何在c++中使用std::atomic进行原子操作_c++原子操作与无锁编程

原子操作的核心在于保证多线程环境下对共享数据的访问是互斥的,避免数据竞争,从而实现线程安全。C++的

std::atomic
模板类提供了这种能力,允许你以原子方式读取、写入和修改变量,而无需显式地使用锁。

解决方案:

使用

std::atomic
的关键在于理解其提供的操作。最常用的包括:

  • load()
    : 原子地读取值。
  • store()
    : 原子地存储值。
  • exchange()
    : 原子地用新值替换旧值,并返回旧值。
  • compare_exchange_weak()
    compare_exchange_strong()
    : 原子地比较当前值与预期值,如果相等则用新值替换,否则不替换。这两个函数是实现无锁算法的基础。
    compare_exchange_weak()
    在某些平台上可能由于伪失败(spurious failure)而返回失败,即使当前值与预期值相等,因此通常在一个循环中使用。
    compare_exchange_strong()
    则保证只有在当前值与预期值不相等时才会返回失败。

下面是一个简单的例子,展示了如何使用

std::atomic
来递增一个共享计数器:

立即学习C++免费学习笔记(深入)”;

#include 
#include 
#include 
#include 

std::atomic counter(0); // 初始化原子计数器

void increment_counter() {
  for (int i = 0; i < 10000; ++i) {
    counter++; // 原子递增操作
  }
}

int main() {
  std::vector threads;
  for (int i = 0; i < 4; ++i) {
    threads.emplace_back(increment_counter);
  }

  for (auto& thread : threads) {
    thread.join();
  }

  std::cout << "Counter value: " << counter << std::endl; // 预期输出:40000
  return 0;
}

在这个例子中,

counter
是一个
std::atomic
类型的原子变量。
counter++
操作会被原子地执行,这意味着即使多个线程同时执行这个操作,
counter
的值也会正确地递增,而不会发生数据竞争。

无锁编程往往比基于锁的编程更加复杂,需要仔细考虑内存模型、数据一致性等问题。

std::atomic
提供了一组工具,可以帮助你构建高效且线程安全的无锁数据结构和算法。

std::atomic
的内存顺序选项是什么?它们如何影响性能?

std::atomic
提供了多种内存顺序选项,用于控制原子操作的同步行为。这些选项包括:

  • std::memory_order_relaxed
    : 最宽松的内存顺序,只保证操作的原子性,不保证任何同步或排序。
  • std::memory_order_consume
    : 保证当前线程能够看到依赖于当前原子变量的其它原子变量的最新值。
  • std::memory_order_acquire
    : 保证当前线程能够看到其它线程在释放(release)同一个原子变量之前的所有写入操作。
  • std::memory_order_release
    : 保证当前线程的所有写入操作对其它线程在获取(acquire)同一个原子变量之后可见。
  • std::memory_order_acq_rel
    : 同时具有 acquire 和 release 的语义,通常用于 read-modify-write 操作,例如
    fetch_add
  • std::memory_order_seq_cst
    : 默认的内存顺序,提供最强的同步保证,保证所有原子操作都按照一个全局的顺序执行。

选择合适的内存顺序对于性能至关重要。

std::memory_order_relaxed
通常是最快的,因为它不需要任何同步,但只有在不需要同步的情况下才能使用。
std::memory_order_seq_cst
提供最强的同步保证,但也是最慢的。通常,应该尽可能使用较弱的内存顺序,只有在需要更强的同步保证时才使用更强的内存顺序。

举个例子,假设有两个线程 A 和 B,共享一个原子变量

x
。线程 A 执行以下操作:

酷兔AI论文
酷兔AI论文

专业原创高质量、低查重,免费论文大纲,在线AI生成原创论文,AI辅助生成论文的神器!

下载
x.store(1, std::memory_order_release);

线程 B 执行以下操作:

int value = x.load(std::memory_order_acquire);

在这个例子中,

std::memory_order_release
保证线程 A 在存储
x
之前的所有写入操作对线程 B 可见。
std::memory_order_acquire
保证线程 B 在读取
x
之后能够看到线程 A 在存储
x
之前的所有写入操作。

如何使用

std::atomic
实现一个简单的自旋锁?

自旋锁是一种忙等待的锁,线程会不断地检查锁是否可用,直到锁被释放。使用

std::atomic
可以很容易地实现一个自旋锁:

#include 

class SpinLock {
public:
  SpinLock() : locked(false) {}

  void lock() {
    while (locked.exchange(true, std::memory_order_acquire));
  }

  void unlock() {
    locked.store(false, std::memory_order_release);
  }

private:
  std::atomic locked;
};

在这个例子中,

locked
是一个
std::atomic
类型的原子变量,用于表示锁的状态。
lock()
函数使用
exchange()
操作原子地尝试获取锁。如果
locked
的值已经是
true
,则
exchange()
操作会返回
true
,线程会继续循环等待。如果
locked
的值是
false
,则
exchange()
操作会将
locked
的值设置为
true
,并返回
false
,线程成功获取锁。
unlock()
函数使用
store()
操作原子地释放锁。

需要注意的是,自旋锁只适用于锁的持有时间很短的情况。如果锁的持有时间很长,线程会浪费大量的 CPU 时间在忙等待上。在锁的持有时间很长的情况下,应该使用互斥锁(

std::mutex
)或条件变量(
std::condition_variable
)。

使用

std::atomic
时可能遇到的陷阱有哪些?

使用

std::atomic
时需要注意以下几个陷阱:

  • 伪共享(False Sharing): 如果多个线程访问相邻的原子变量,即使这些变量之间没有逻辑关系,也可能导致性能下降。这是因为 CPU 缓存行是以行为单位进行缓存的,如果多个线程访问同一个缓存行中的不同变量,会导致缓存行的频繁失效和重新加载。为了避免伪共享,可以将原子变量分散到不同的缓存行中,例如使用填充(padding)。
  • ABA 问题: 在无锁算法中,如果一个原子变量的值从 A 变为 B,然后再变回 A,可能会导致一些问题。例如,
    compare_exchange_weak()
    可能会错误地认为原子变量的值没有改变,从而导致算法出错。为了解决 ABA 问题,可以使用版本号或指针标记。
  • 内存泄漏: 在无锁数据结构中,如果一个线程在释放一个节点之前崩溃,可能会导致内存泄漏。为了避免内存泄漏,可以使用 Hazard Pointer 或 RCU(Read-Copy-Update)等技术。
  • 死锁: 虽然
    std::atomic
    本身可以避免数据竞争,但如果使用不当,仍然可能导致死锁。例如,如果两个线程互相等待对方释放锁,就会导致死锁。

总之,使用

std::atomic
需要仔细考虑各种因素,才能编写出高效且线程安全的无锁代码。

相关文章

编程速学教程(入门课程)
编程速学教程(入门课程)

编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

338

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

542

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

53

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

197

2025.08.29

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

535

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

21

2026.01.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

Java编译相关教程合集
Java编译相关教程合集

本专题整合了Java编译相关教程,阅读专题下面的文章了解更多详细内容。

9

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 7.2万人学习

C 教程
C 教程

共75课时 | 4.1万人学习

C++教程
C++教程

共115课时 | 13.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号