C++并发实战16: std::atomic原子操作

本文探讨了C++中并发编程时如何使用std::atomic来避免data race。介绍了std::atomic_flag和std::atomic,强调了它们在保护共享数据时的重要性,同时阐述了原子操作的限制和应用场景,包括自旋锁的实现。文章还提到了C风格的原子操作,并指出原子类型对于特定数据类型(如整数和指针)的特化支持。
摘要由CSDN通过智能技术生成

     C++中对共享数据的存取在并发条件下可能会引起data race的undifined行为,需要限制并发程序以某种特定的顺序执行,有两种方式:使用mutex保护共享数据,原子操作:针对原子类型操作要不一步完成,要么不做,不可能出现操作一半被切换CPU,这样防止由于多线程指令交叉执行带来的可能错误。非原子操作下,某个线程可能看见的是一个其它线程操作未完成的数据。

1  关于bool的原子化

   1.1 std::atomic_flag是一个bool原子类型有两个状态:set(flag=true) 和 clear(flag=false),必须被ATOMIC_FLAG_INIT初始化此时flag为clear状态,相当于静态初始化。一旦atomic_flag初始化后只有三个操作:test_and_set,clear,析构,均是原子化操作。atomic_flag::test_and_set检查flag是否被设置,若被设置直接返回true,若没有设置则设置flag为true后再返回false。atomic_clear()清楚flag标志即flag=false。不支持拷贝、赋值等操作,这和所有atomic类型一样,因为两个原子类型之间操作不能保证原子化。atomic_flag的可操作性不强导致其应用局限性,还不如atomic<bool>。

    使用atomic_flag作为简单的自旋锁例子:本线程可以对flag设置了就跳出循环,避免使用mutex导致线程阻塞

#include <iostream>       // std::cout
#include <atomic>         // std::atomic_flag
#include <thread>         // std::thread
#include <vector>         // std::vector
#include <sstream>        // std::stringstream

std::atomic_flag lock_stream = ATOMIC_FLAG_INIT;//flag处于clear状态,没有被设置过
std::stringstream stream;

void append_number(int x) {
  while (lock_stream.test_and_set()) {}//检查并设置是个原子操作,如以前没有设置过则退出循环,
    //每个线程都等待前面一个线程将lock_stream状态清楚后跳出循环
  stream << "thread #" << x << '\n'; 
  lock_stream.clear();}
int main (){ 
  std::vector<std::thread> threads; 
  for (int i=1; i<=10; ++i) 
     threads.push_back(std::thread(append_number,i)); 
  for (auto& th : threads) th.join(); std::cout << stream.str(); return 0;
}

        采用class封装可以用于lock_guard或unique_lock,但是最好不要将此用于任何竞态条件下,这是一个busy loop!

class spinlock_mutex
{
   std::atomic_flag flag;
 public:
   spinlock_mutex():
   flag(ATOMIC_FLAG_INIT){}
   void lock()
   {
     while(flag.test_and_set(std::memory_order_acquire));
   }
   void unlock()
   {
     flag.clear(std::memory_order_release);
   }
};
  

 

2 atomic<T>模板类,生成一个T类型的原子对象,并提供了系列原子操作函数。其中T是trivially  copyable type满足:要么全部定义了拷贝/移动/赋值函数,要么全部没定义;没有虚成员;基类或其它任何非static成员都是trivally copyable。典型的内置类型bool、int等属于trivally copyable。再如class triviall{public: int x};也是。T能够被memcpy、memcmp函数使用,从而支持compare/exchange系列函数。有一条规则:不要在保护数据中通过用户自定义类型T通过参数指针或引用使得共享数据超出保护的作用域。atomic<T>编译器通常会使用一个内部锁保护,而如果用户自定义类型T通过参数指针或引用可能产生死锁。总之限制T可以更利于原子指令。注意某些原子操作可能会失败,比如atomic<float>、atomic<double>在compare_exchange_strong()时和expected相等但是内置的值表示形式不同于expected,还是返回false,没有原子算术操作针对浮点数;同理一些用户自定义的类型T由于内存的不同表示形式导致memcmp失败,从而使得一些相等的值仍返回false。

atomic<T>的成员函数:

template < class T > struct atomic {
    bool is_lock_free() const volatile;//判断atomic<T>中的T对象是否为lock free的,若是返回true。lock free(锁无关)指多个线程并发访问T不会出现data race,任何线程在任何时刻都可以不受限制的访问T
    bool is_lock_free() const;
    atomic() = default;//默认构造函数,T未初始化,可能后面被atomic_init(atomic<T>* obj,T val )函数初始化
    constexpr atomic(T val);//T由val初始化
    atomic(const atomic &) = delete;//禁止拷贝
    atomic & operator=(const atomic &) = delete;//atomic对象间的相互赋值被禁止,但是可以显示转换再赋值,如atomic<int> a=static_cast<int>(b)这里假设atomic
  • 17
    点赞
  • 122
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值