码迷,mamicode.com
首页 > 编程语言 > 详细

无锁同步-C++11之Atomic和CAS

时间:2016-07-20 06:27:51      阅读:283      评论:0      收藏:0      [点我收藏+]

标签:

1、概要

      本文是无锁同步系列文章的第一篇,主要探讨C++11中的Atomic。

      我们知道在C++11中引入了mutex和方便优雅的lock_guard。但是有时候我们想要的是性能更高的无锁实现,下面我们来讨论C++11中新增的原子操作类Atomic,我们可以利用它巧妙地实现无锁同步。

2、传统的线程同步

 1 #include <thread>
 2 #include <mutex>
 3 
 4 #include <iostream>
 5 
 6 using namespace std;
 7 
 8 mutex g_mutex;
 9 int g_count = 0;
10 
11 int main()
12 {
13     thread thr1([]() {
14         for (int i = 0;i < 5;i++) {
15             lock_guard<mutex> lock(g_mutex);    //
16             g_count += 10;
17         }
18     });
19 
20     thread thr2([]() {
21         for (int i = 0;i < 5;i++) {
22             lock_guard<mutex> lock(g_mutex);    //
23             g_count += 20;
24         }
25     });
26 
27     thr1.join();
28     thr2.join();
29 
30     cout << g_count << endl;
31 
32 33 }

       在上述例子中,如果把①②的锁注释后,我们可能无法得到正确的结果。原因是C++并没有给我们保证+=操作具有原子性(其本质应该是读-加-写3个操作)。

3、Atomic

       C++11给我们带来的Atomic一系列原子操作类,它们提供的方法能保证具有原子性。这些方法是不可再分的,获取这些变量的值时,永远获得修改前的值或修改后的值,不会获得修改过程中的中间数值。

       这些类都禁用了拷贝构造函数,原因是原子读和原子写是2个独立原子操作,无法保证2个独立的操作加在一起仍然保证原子性。

       这些类中,最简单的是atomic_flag(其实和atomic<bool>相似),它只有test_and_set()和clear()方法。其中,test_and_set会检查变量的值是否为false,如果为false则把值改为true。

       除了atomic_flag外,其他类型可以通过atomic<T>获得。atomic<T>提供了常见且容易理解的方法:

  1. store
  2. load
  3. exchange
  4. compare_exchange_weak
  5. compare_exchange_strong

       其中,compare_exchange_weak和compare_exchange_strong则是著名的CAS。参数会要求在这里传入期待的数值和新的数值。它们对比变量的值和期待的值是否一致,如果是,则替换为用户指定的一个新的数值。如果不是,则将变量的值和期待的值交换

       weak版本的CAS允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。但它比起strong有更高的性能。

3、例子

       下面举个简单的例子说明一下CAS的用途。

       这个例子从《C++并发编程》摘抄而来,这是一个支持并发访问的栈,但却没有使用锁。因而具备非常高的性能。我们把这称之为无锁栈。

 1 template<typename T>
 2 class lock_free_stack
 3 {
 4 private:
 5   struct node
 6   {
 7     std::shared_ptr<T> data;
 8     std::shared_ptr<node> next;
 9     node(T const& data_):
10       data(std::make_shared<T>(data_))
11     {}
12   };
13 
14   std::shared_ptr<node> head;
15 public:
16   void push(T const& data)
17   {
18     std::shared_ptr<node> const new_node=std::make_shared<node>(data);
19     new_node->next=head.load();
20     while(!std::atomic_compare_exchange_weak(&head,
21         &new_node->next,new_node));
22   }
23   std::shared_ptr<T> pop()
24   {
25     std::shared_ptr<node> old_head=std::atomic_load(&head);
26     while(old_head && !std::atomic_compare_exchange_weak(&head,
27         &old_head,old_head->next));
28     return old_head ? old_head->data : std::shared_ptr<T>();
29   }
30 };

       这段代码有很多巧妙之处。

Push

       在push方法里,atomic_compare_exchange_weak如果失败,证明有其他线程更新了栈顶,而这个时候被其他线程更新的新栈顶值会被更新到new_node->next中,因此循环可以直接再次尝试压栈。

Pop

       在pop方法里,如果有2条线程同时执行到25行。

       线程1使用完毕后对data进行delete,这是第二条线程仍然在执行第26、27行,如果没有old_head是否为空的判断,old_head->next可能会导致无法预料的访问。

 

无锁同步-C++11之Atomic和CAS

标签:

原文地址:http://www.cnblogs.com/dengzz/p/5686866.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!