C++11尺度库 原子变量 <atomic> 梳理

打印 上一主题 下一主题

主题 833|帖子 833|积分 2499

目录



C++11提供了一个原子范例std::atomic,通过这个原子范例管理的内部变量就可以称之为原子变量.
原子范例只支持bool、char、int、long、指针等整型范例作为模板参数(不支持浮点范例和复合范例)。
原子操纵的概念

在原子操纵过程中,线程不会发生调度。原子操纵是一种不可中断的操纵,一旦开始执行,就会一直运行到结束,不会被其他线程打断。因此,在原子操纵期间,操纵系统的线程调度器不会将CPU分配给其他线程,以确保原子操纵的完备性和正确性。
CAS实现原理

CAS(Compare-And-Swap 或 Compare-And-Set)是一种原子操纵,用于实现多线程编程中的同步机制。它的基本原理是通过比力和交换来确保一个变量在多个线程访问时的正确性。CAS是很多并发数据结构和算法的基础,比如无锁队列、无锁栈等。
CAS操纵的伪代码:
  1. 硬件提供的原子性支持,如汇编lock指令等 //整个代码执行都是不可中断的(不是串行)
  2. (asm::lock:)
  3. bool compare_and_swap(int* addr, int expected, int new_value) {
  4.     int current = *addr; // 读取当前值
  5.     if (current == expected) { //比较当前值和预期值
  6.         *addr = new_value; // 如果当前值等于预期值,则更新
  7.         return true;
  8.     }
  9.     return false; // 否则,不更新
  10. }
  11. (asm::unlock:)
复制代码
使用CAS完成变量的原子操纵:
  1. // 共享的整数变量
  2. int shared_var = 10; //ABA问题:无法得知shared_var的全程状态.被修改去又修改回,起止状态不变,过程改变,状态在过程中发生改变CAS却误以为没有改变过.可能导致发生一些隐晦的错误.
  3. //避免ABA,有做变量版本号机制(tag,flag等),或更复杂的实现. -- 有的说回退机制可以:不可以,单单回退机制无法解决ABA问题
  4. void thread_function() {
  5.     int expected_value = 10; // 希望 shared_var 的当前值是一般是shared_var的原始值,即10
  6.     int new_value = 20;      // 目的 将shared_var 更新为 20
  7.     // 使用 CAS 操作来原子地更新 shared_var
  8.     bool success = compare_and_swap(&shared_var, expected_value, new_value);
  9.     if (success) {
  10.         // CAS 操作成功
  11.         // 这里可以继续处理其他逻辑
  12.     } else {
  13.         // CAS 操作失败,shared_var 的值不是我们预期的 expected_value
  14.         // 可能需要重试或处理其他情况
  15.     }
  16. }
复制代码
CAS 操纵的包管

尽管大概会发生 CPU 上下文切换,但 CAS 操纵的包管在于其执行过程中的不可中断性。纵然发生了 CPU 上下文切换,操纵系统和处理器会包管 CAS 操纵的执行过程是原子的,其他线程或处理器无法在 CAS 操纵期间对其操纵的内存位置进行修改。
(CAS == 逻辑检查变量状态+硬件支持语句原子性)
lock和锁的概念

汇编中的lock指令前缀和编程中的锁(如互斥锁)虽然在概念上都涉及到同步和确保操纵的原子性,但它们是不同的东西,作用机制和应用场景也不同。

  • lock指令前缀用于多处理器系统中的汇编指令,确保特定的内存操纵在多个处理器上是原子的。它的作用是锁住总线或使用缓存一致性协议,确保在指令执行期间其他处理器无法访问涉及的内存位置。lock前缀常用于需要原子操纵的低级同步机制中,例如在实现原子性增减、比力交换等操纵时。
  • 编程中的锁(如互斥锁、读写锁)是一种高级同步原语,用于确保同一时刻只有一个线程可以访问临界区(共享资源)。用于保护临界区,防止数据竞争,确保线程安全。常见的应用场景包括多线程程序中的共享数据访问、数据库中的事务管理等。
atomic模板类

构造函数
  1. atomic() noexcept = default;
  2. constexpr atomic( T desired ) noexcept;
  3. atomic( const atomic& ) = delete;      //禁止拷贝
复制代码
desired:用以初始化的值
空对象初始化方式:
MSVC中类成员atomic范例答应带有缺省值(MSVC优化).但这不是尺度C++行为.atomic范例的成员只能在构造函数中完成初始化.
而GCC中不答应使用缺省值,是由编译器实现的.
假如我们要本身实现一个不答应使用缺省值的范例,则可以显式定义构造函数+explicit

公共成员函数:


  • operator=
  1. //模拟原生变量的行为,赋值给原生变量
  2. T operator=( T desired ) noexcept;
  3. T operator=( T desired ) volatile noexcept;
  4. //禁止拷贝赋值重载
  5. atomic& operator=( const atomic& ) = delete;
  6. atomic& operator=( const atomic& ) volatile = delete;
复制代码

  • store
和operator=功能一样,将数据存储到原生变量中,没有返回值
  1. void store( T desired, std::memory_order order = std::memory_order_seq_cst ) noexcept;
  2. void store( T desired, std::memory_order order = std::memory_order_seq_cst ) volatile noexcept;
复制代码
desired:存储到原子变量中的值
order  :程序代码内存执行顺序约束(跨平台使用)
volatile:包管内存可见性,修饰函数时表示可以通过该函数访问到volatile修饰的变量.


  • load
取出原生变量的值.
  1. T load( std::memory_order order = std::memory_order_seq_cst ) const noexcept;
  2. T load( std::memory_order order = std::memory_order_seq_cst ) const volatile noexcept;
复制代码

  • operator T()
  1. operator T() const volatile noexcept;
  2. operator T() const noexcept;
复制代码
类范例转换运算符重载.将原子变量范例转化成T范例.
意思是,通过原子变量得到的值就是T范例的值,即得到原生变量的值.等同于load().
示例:


  • exchange
  1. T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
  2. T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;
复制代码
和store()功能类似,将数据存储/覆盖到原生变量中.
不同的功能是,exchange还会返回被覆盖的旧数据.

  • compare_exchange_weak & compare_exchange_strong
使用C++11原子量实现自旋锁 - 兔晓侠 - 博客园 (cnblogs.com)
compare_exchange_weak 与 compare_exchange_strong 主要的区别在于内存中的值与expected相等的时间,CAS操纵是否一定能乐成.
compare_exchange_weak有概率会返回失败,而compare_exchange_strong则一定会乐成
因此,compare_exchange_weak必须与循环搭配使用来包管在失败的时间重试CAS操纵。得到的利益是在某些平台上compare_exchange_weak性能更好。按照上面的模型,我们本来就要和while搭配使用,可以使用compare_exchange_weak。
最后内存序的选择没有特殊需求直接使用默认的std::memory_order_seq_cst。


  • atomic提供的一组用于对原子变量进行修改的特化函数
atomic - C++ Reference (cplusplus.com)

分别是 加、减、按位与、按位或、按位异或、自增、自减、赋值类 操纵。
各个 operator 对应的 fetch_ 操纵表
操纵符操纵符重载函数等级的成员函数整形指针其他+atomic:perator+=atomic::fetch_add是是否-atomic:perator-=atomic::fetch_sub是是否&atomic:perator&=atomic::fetch_and是否否|atomic:perator|=atomic::fetch_or是否否^atomic:perator^=atomic::fetch_xor是否否


  • C++11还为常用的atomic提供了别名
std::atomic - cppreference.com
有很多,举例出一部门
别名原始范例定义atomic_bool(C++11)std::atomicatomic_char(C++11)std::atomicatomic_schar(C++11)std::atomicatomic_uchar(C++11)std::atomicatomic_short(C++11)std::atomicatomic_ushort(C++11)std::atomicatomic_int(C++11)std::atomicatomic_uint(C++11)std::atomicatomic_long(C++11)std::atomicatomic_ulong(C++11)std::atomicatomic_llong(C++11)std::atomicatomic_ullong(C++11)std::atomicatomic与互斥锁的效率比对

例程:
[code]#include #include #include #include #include using namespace std;struct Counter{  Counter():m_value(0){}  void increment()  {    for (int i = 0; i < 100000000; ++i)    {      lock_guard locker(m_mutex);      m_value++;    }  }  void decrement()  {    for (int i = 0; i < 100000000; ++i)    {      lock_guard locker(m_mutex);      m_value--;    }  }  int m_value = 0;  //std::atomic m_value ;  mutex m_mutex;};int main(){  Counter c;  auto increment = bind(&Counter::increment, &c);  auto decrement = bind(&Counter::decrement, &c);  thread t1(increment);  thread t2(decrement);  t1.join();  t2.join();  std::cout

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

写过一篇

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表