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

2014-11-24 07:10:55 · 作者: · 浏览: 0

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

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

#include 
  
          // std::cout
#include 
   
     // std::atomic_flag #include 
    
      // std::thread #include 
     
       // std::vector #include 
      
        // 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
       
         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_guar或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是trivially copyable type满足:要么全部定义了拷贝/移动/赋值函数,要么全部没定义;没有虚成员;基类或其它任何非static成员都是trivally copyable。典型的内置类型bool、int等属于trivally copyable。再如class triviall{public: int x};也是。

atomic 的成员函数:

template < class T > struct atomic {
    bool is_lock_free() const volatile;//判断atomic
  
   中的T对象是否为lock free的,若是返回true。lock free(锁无关)指多个线程并发访问T不会出现data race,任何线程在任何时刻都可以不受限制的访问T
    bool is_lock_free() const;
    atomic() = default;//默认构造函数,T未初始化,可能后面被atomic_init(atomic
   
    * obj,T val )函数初始化 constexpr atomic(T val);//T由val初始化 atomic(const atomic &) = delete;//禁止拷贝 atomic & operator=(const atomic &) = delete;//atomic对象间的相互赋值被禁止,但是可以显示转换再赋值,如atomic
    
      a=static_cast
     
      (b)这里假设atomic
      
        b atomic & operator=(const atomic &) volatile = delete;//atomic间不能赋值 T operator=(T val) volatile;//可以通过T类型对atomic赋值,如:atomic
       
         a;a=10; T operator=(T val); operator T() const volatile;//读取被封装的T类型值,是个类型转换操作,默认内存序是memory_order_seq需要其它内存序则调用load operator T() const;//如:atomic
        
          a,a==0或者cout<
         
          

cplusplus给出的例子之一:

// atomic::compare_exchange_weak example:
#include 
           
                   // std::cout
#include 
            
              // std::atomic #include 
             
               // std::thread #include 
              
                // std::vector // a simple global linked list: struct Node { int value; Node* next; }; std::atomic
               
                 list_head (nullptr); void append (int val) { // append an element to the list Node* newNode = new Node {val,list_head}; // next is the same as: list_head = newNode, but in a thread-safe way: while (!list_head.compare_exchange_weak(newNode->next,newNode)) {} // (with newNode->next updated accordingly if some other thread just appended another node) } int main () { // spawn 10 threads to fill the linked list: std::vector
                
                  threads; for (int i=0; i<10; ++i) threads.push_back(std::thread(append,i)); for (auto& th : threads) th.join(); // print contents: for (Node* it = list_head; it!=nullptr; it=it->next) std::cout << ' ' << it->value; std::cout << '\n'; // cleanup: Node* it; while (it=list_head) {list_head=it->next; delete it;} return 0; }
                
               
              
             
            
           


程序输出:

 9 8 7 6 5 4 3 2 1 0


3 std::atomic针对整数和指针的特化:

不能像传统那样拷贝和赋值,可以通过内置成员函数load(),store(),exchange()完成赋值,支持复合赋值运算,自增自减运算,还有特有的fetch系列函数

整型特化: