无锁编程[1]__关于CAS等原子操作,用CAS实现互斥锁,CAS是无锁编程的基础,举例:设计无锁并发堆栈...

转自:http://blog.163.com/xychenbaihu@yeah/blog/static/1322296552013373236132/

 

关于CAS等原子操作:
         在开始说无锁队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。

这个操作用C语言来描述就是下面这个样子:
看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。

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

这个操作可以变种为返回bool值的形式(返回 bool值的好处在于,可以调用者知道有没有更新成功):
bool compare_and_swap (int *accum, int *dest, int newval)
{   
      if ( *accum == *dest ) 
      {       
           *dest = newval;       
           return true;   
      }   
      return false; 

与CAS相似的还有下面的原子操作:

Fetch And Add,一般用来对变量做 +1 的原子操作

参看:http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520133543527567/

Test-and-set,写值到某个内存位置并传回其旧值。汇编指令BST 
Test and Test-and-set,用来低低Test-and-Set的资源争夺情况


注:在实际的C/C++程序中,CAS的各种实现版本如下:

1、GCC的CAS,GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins)
1)bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...) 
2)type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)

2、C++11中的CAS,C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library)
1)template< class T > bool atomic_compare_exchange_weak( std::atomic* obj,T* expected, T desired ); 
2)template< class T > bool atomic_compare_exchange_weak( volatile std::atomic* obj,T* expected, T desired );

 

用cas实现锁:
#define lock(lkp) do{  \
    while(!__sync_bool_compare_and_swap(lkp, 0, 1)){    \
        sched_yield();  \
    }   \
}while(0)

其中:sched_yield()是让出CPU。

#define unlock(lkp) do{    \
    *(lkp) = 0;  \
}while(0)

 

#define try_lock(lkp) ({   \
    (__sync_bool_compare_and_swap(lkp, 0, 1) ? 0 : -1);   \
})

 

设计无锁并发堆栈

既然了解了 CAS,现在就来设计一个并发堆栈。这个堆栈没有锁;这种无锁的并发数据结构也称为非阻塞数据结构清单 12 给出代码接口。


清单 12. 基于链表的非阻塞堆栈实现 

                           
template  
class Stack { 
    typedef struct Node { 
                          T data;
                          Node* next;
                          Node(const T& d) : data(d), next(0) { } 
                        } Node;
    Node *top; 
    public: 
       Stack( ) : top(0) { }
       void push(const T& data);
       T pop( ) throw (…); 
};


清单 13. 在非阻塞堆栈中压入数据

                         
void Stack::push(const T& data) 
{ 
    Node *n = new Node(data); 
    while (1) { 
        n->next = top;
        if (__sync_bool_compare_and_swap(&top, n->next, n)) { // CAS
            break;
        }
    }
}

 

压入(Push)操作做了什么?

        从单一线程的角度来看,创建了一个新节点,它的 next 指针指向堆栈的顶部。接下来,调用 CAS 内置函数,把新的节点复制到 top 位置。 从多个线程的角度来看,完全可能有两个或更多线程同时试图把数据压入堆栈。假设线程 A 试图把 20 压入堆栈,线程 B 试图压入 30,而线程 A 先获得了时间片。但是,在 n->next = top 指令结束之后,调度程序暂停了线程 A。现在,线程 B 获得了时间片(它很幸运),它能够完成 CAS,把 30 压入堆栈后结束。接下来,线程 A 恢复执行,显然对于这个线程 *top 和 n->next 不匹配,因为线程 B 修改了 top 位置的内容。因此,代码回到循环的开头,指向正确的 top 指针(线程 B 修改后的),调用 CAS,把 20 压入堆栈后结束。整个过程没有使用任何锁。


清单 18. 从非阻塞堆栈弹出数据

                           
T Stack::pop( ) 
{ 
    while (1) { 
        Node* result = top;
        if (result == NULL) 
           throw std::string(“Cannot pop from empty stack”);      
        if (top && __sync_bool_compare_and_swap(&top, result, result->next)) { // CAS
            return result->data;
        }
    }
}

这样,即使线程 B 在线程 A 试图弹出数据的同时修改了堆栈顶,也可以确保不会跳过堆栈中的元素。

 

 

结束语

       本系列讨论了如何设计支持并发访问的数据结构。您已经看到,设计可以基于互斥锁,也可以是无锁的。无论采用哪种方式,要考虑的问题不仅仅是这些数据结构的基本功能 — 具体来说,必须一直记住线程会争夺执行权,要考虑线程重新执行时如何恢复操作。目前,解决方案(尤其是无锁解决方案)与平台/编译器紧密相关。请研究用于实现线程和锁的 Boost 库,阅读 John Valois 关于无锁链表的文章(见 参考资料 中的链接)。C++0x 标准提供了 std::thread 类,但是目前大多数编译器对它的支持很有限,甚至不支持它。 

 

原文参考:

http://coolshell.cn/articles/8239.html

http://www.ibm.com/developerworks/cn/aix/library/au-multithreaded_structures2/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值