详细解析了 compare_exchange_weak -----并发编程入门(三): 使用C++11实现无锁stack(lock-free stack)

前几篇文章,我们讨论了如何使用mutex保护数据及使用使用condition variable在多线程中进行同步。然而,使用mutex将会导致一下问题:

  • 等待互斥锁会消耗宝贵的时间 — 有时候是很多时间。这种延迟会损害系统的scalability。尤其是在现在可用的core越多越多的情况下。
  • 低优先级的线程可以获得互斥锁,因此阻碍需要同一互斥锁的高优先级线程。这个问题称为 优先级倒置(priority inversion )
  • 可能因为分配的时间片结束,持有互斥锁的线程被取消调度。这对于等待同一互斥锁的其他线程有不利影响,因为等待时间现在会更长。这个问题称为 锁护送(lock convoying)

互斥锁的问题还不只这些。早在1994年10月,John D. Valois 在拉斯维加斯的并行和分布系统系统国际大会上的一篇论文—《 Implementing Lock-Free Queues 》已经研究了无锁队列的实现,有兴趣的可以拜读一下。

实现无锁数据结构的基础是CAS:Compare & Set,或是 Compare & Swap。CAS用C语言描述的代码(来自 Wikipedia Compare And Swap)

int compare_and_swap (int* reg, int oldval, int newval) 
{
  ATOMIC();
  int old_reg_val = *reg;
  if (old_reg_val == oldval) 
     *reg = newval;
  END_ATOMIC();
  return old_reg_val;
}

CAS是个原子操作,保证了如果需要更新的地址没有被他人改动多,那么它可以安全的写入。而这也是我们对于某个数据或者数据结构加锁要保护的内容,保证读写的一致性,不出现dirty data。现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是  CMPXCHG  汇编指令 现在,我们将使用CAS来实现无锁的stack,然后你就能够理解CAS的用法了。

C++11中CAS实现:

template< class T>
struct atomic<T*>
{
public:
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order success,
                            std::memory_order failure );
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order success,
                            std::memory_order failure ) volatile;
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order order =
                                std::memory_order_seq_cst );
bool compare_exchange_weak( T& expected, T desired,
                            std::memory_order order =
                                std::memory_order_seq_cst ) volatile;
bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order success,
                              std::memory_order failure );
bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order success,
                              std::memory_order failure ) volatile;    
bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order order =
                                  std::memory_order_seq_cst );
bool compare_exchange_strong( T& expected, T desired,
                              std::memory_order order =
                                  std::memory_order_seq_cst ) volatile;
...
};
Please refer to http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange to more information.

对上面的版本进行一下说明。翻译自上述url:

Atomically compares the value stored in *this with the value of expected , and if those are equal, replaces the former with desired (performs read-modify-write operation). Otherwise, loads the actual value stored in *this into expected (performs load operation).

自动的比较*this的值和expect的值,如果相等,那么将*this的值替换为desired的值(进行读-修改-写操作)。否则如果不相等,那么将*this的值存到expected处。

伪码就是:

if *this == expected:

    *this = desired;

else:

    expected = *this;
The memory models for the read-modify-write and load operations are success and failure respectively. In the (2) and (4) versions order is used for both read-modify-write and load operations, except that std::memory_order_release and std::memory_order_relaxed are used for the load operation if order == std:: memory_order_acq_rel , or order == std:: memory_order_release respectively.

success 对应于read-modify-write的内存模型;failure则对应于失败时的load。对于order = std:: memory_order_seq_cst 的函数,那么该memory order适用于read-modify-write and load,除非是如果 order == std:: memory_order_acq_rel ,那么load将使用 std::memory_order_release ;如果 order == std:: memory_order_release ,那么load将使用 std::memory_order_relaxed

更多信息memory order请阅读:http://en.cppreference.com/w/cpp/atomic/memory_order

The weak forms (1-2) of the functions are allowed to fail spuriously, that is, act as if * this ! = expected even if they are equal. When a compare-and-exchange is in a loop, the weak version will yield better performance on some platforms. When a weak compare-and-exchange would require a loop and a strong one would not, the strong one is preferable.

weak形式允许假失败,该函数直接比较原子对象所封装的值与参数 expected 的物理内容,所以某些情况下,对象的比较操作在使用 operator==() 判断时相等,但 compare_exchange_weak 判断时却可能失败,因为对象底层的物理内容中可能存在位对齐或其他逻辑表示相同但是物理表示不同的值(比如 true 和 2 或 3,它们在逻辑上都表示"真",但在物理上两者的表示并不相同)。可以虚假的返回false(和expected相同)。若本atomic的T值和expected相同则用val值替换本atomic的T值,返回true;若不同则用本atomic的T值替换expected,返回false。  
与compare_exchange_weak 不同, strong版本的 compare-and-exchange 操作不允许(spuriously 地)返回 false,即原子对象所封装的值与参数 expected 的物理内容相同,比较操作一定会为 true。不过在某些平台下,如果算法本身需要循环操作来做检查, compare_exchange_weak 的性能会更好。因此对于某些不需要采用循环操作的算法而言, 通常采用compare_exchange_strong 更好

下面代码部分来自http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange。

#include <atomic>
#include <string>
#include <iostream>
using namespace std;
template<typename T>
struct node
{
    T data;
    node* next;
    node(const T& data) : data(data), next(nullptr) {}
};

template<typename T>
class stack
{
    std::atomic<node<T>*> head;

 public:
    stack():head(nullptr){}
    void push(const T& data);
    T pop();
};
注意在这里添加了stack的构造函数,把head初始化为nullptr。如果不初始化它为nullptr,那么使用链表存储的stack将无法确定终点在哪儿。。。

首先看一下push的实现:

void push(const T& data)
    {
        node<T>* new_node = new node<T>(data);

        // put the current value of head into new_node->next
        new_node->next = head.load(std::memory_order_relaxed);

        // now make new_node the new head, but if the head
        // is no longer what's stored in new_node->next
        // (some other thread must have inserted a node just now)
        // then put that new head into new_node->next and try again
        while(!head.compare_exchange_weak(new_node->next,
                                          new_node,
                                          std::memory_order_release,
                                          std::memory_order_relaxed))
                ; // the body of the loop is empty
    }
主要是理解这两句:
head.compare_exchange_weak(new_node->next,
                           new_node,
可以简单用一下代码来概括该调用的效果:
if ( head == new_node->new){
     head = new_node;
     return true;
}
else{
    new_node->next = head;
    return false;
}
因此,如果没有其他的线程push,那么head将指向当前的new_node,push完成。否则,说明其他线程push过新数据,那么将当前push的新节点重新放到顶端,此时的head是最新的head。这样,通过CAS,我们可以实现了thread-safe stack。

接下来看一下pop:

T pop()
    {
        while(1){
            auto result = head.load(std::memory_order_relaxed);
            if (result == nullptr)
              throw std::string("Cannot pop from empty stack");
            if(head.compare_exchange_weak(result,result->next,
                                          std::memory_order_release,
                                          std::memory_order_relaxed))
               return result->data;
        }
    }
我们为什么要限制result != nullptr?因为有可能当前stack仅有一个元素,线程B在pop时被调度,线程A pop成功,那么线程B再pop就会出问题。

其实,上述的pop可以简化,因为result其实在failed时候已经更新为head了。因此简化代码可以是:

T pop()
    {
      auto result = head.load(std::memory_order_relaxed);
      while( result != nullptr && !head.compare_exchange_weak(result,result->next,
                                          std::memory_order_release,
                                          std::memory_order_relaxed));
      if( result != nullptr)
        return result->data;
      else
        throw std::string("Cannot pop from empty stack");
    }

尊重原创,转载请注明出处: anzhsoft http://blog.csdn.net/anzhsoft/article/details/19125619

 
例子代码:g++ -std=c++11 -o test testatomic.cpp -pthread
 
#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
#include <unistd.h>
#include <atomic>
#include <memory>
using namespace std;
template<typename T>
class lock_free_stack//栈的底层数据结构采用单向链表实现
{
private:
    struct node
    {
        std::shared_ptr<T> data;//这里采用shared_ptr管理的好处在于:若栈内存放对象pop中return栈顶对象可能拷贝异常,栈内只存储指针还可以提高性能
        node* next;
        node(T const& data_):data(std::make_shared<T>(data_)) , next(nullptr)//注意make_shared比直接shared_ptr构造的内存开销小
        {}
    };
    std::atomic<node*> head;//采用原子类型管理栈顶元素,主要利用atomic::compare_exchange_weak实现lock free
public:
    void push(T const& data)
    {
        node* const new_node=new node(data);
        new_node->next=head.load();//每次从链表头插入
        while(!head.compare_exchange_weak(new_node->next,new_node));//若head==new_node->next则更新head为new_node,返回true结束循环,插入成功; 若head!=new_node->next表明有其它线程在此期间对head操作了,将new_node->next更新为新的head,返回false,继续进入下一次while循环。这里采用atomic::compare_exchange_weak比atomic::compare_exchange_strong快,因为compare_exchange_weak可能在元素相等的时候返回false所以适合在循环中,而atomic::compare_exchange_strong保证了比较的正确性,不适合用于循环
    }
  /*  std::shared_ptr<T> pop()
    {
        node* old_head=head.load();//拿住栈顶元素,但是可能后续被更新,更新发生在head!=old_head时
        while(old_head &&!head.compare_exchange_weak(old_head,old_head->next));//这里注意首先要先判断old_head是否为nullptr防止操作空链表,然后按照compare_exchange_weak语义更新链表头结点。若head==old_head则更新head为old_head->next并返回true,结束循环,删除栈顶元素成功;若head!=old_head表明在此期间有其它线程操作了head,因此更新old_head为新的head,返回false进入下一轮循环,直至删除成功。
        return old_head ? old_head->data : std::shared_ptr<T>();//这里注意空链表时返回的是一个空的shared_ptr对象
    }//这里只是lock free,由于while循环可能无限期循环不能在有限步骤内完成,故不是wait free
   
    */
    std::shared_ptr<T> pop()//T pop()
    {
        while(1){
            auto result = head.load(std::memory_order_relaxed);
            if (result == nullptr)
             {     //continue;//throw std::string("Cannot pop from empty stack");
                return nullptr; 
            }
            if(head.compare_exchange_weak(result,result->next,
                                          std::memory_order_release,
                                          std::memory_order_relaxed))
               return result->data;
        }
    }
};

std::atomic<bool> ready(false);
std::atomic<bool> winner(false);
lock_free_stack<int> lfs;
void count1m (int id)
{
    while (!ready) {}                  // wait for the ready signal
    for (int i = 0; i < 1000000; ++i) {}   // go!, count to 1 million
    if (!winner.exchange(true)) { std::cout << "thread #" << id << " won!\n"; }
};
void putdata()
{
 
   int value=1;
   for(int i=0;i<100000;i++)
   {
       value=i;
       lfs.push(value);
       usleep(1);
    }
}
void popdata()
{
 
   shared_ptr<int> value=nullptr;
  
    for(int i=0;i<100000;i++)
   {
       value=lfs.pop();
       if(value!=nullptr)
        {
           // std::cout<<"i:"<<i<<" value"<<*value<<std::endl;
        }else
        {
          cout<<"nullptr"<<"i:"<<i<<endl;
         
        }
    } 
  
 
}

int main ()
{
 /*
    std::vector<std::thread> threads;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    for (int i = 1; i <= 10; ++i) threads.push_back(std::thread(count1m,i));
    ready = true;
    for (auto& th : threads) th.join();
*/

 
  
   thread  thread_put=thread(putdata);
   thread  thread_pop=thread(popdata);
  
   thread_put.join();
   thread_pop.join();
  
    return 0;
}
  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值