实现无锁的栈

实现无锁的栈

1.关于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相似的还有下面的原子操作:(这些东西大家自己看Wikipedia吧)

注:在实际的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)Windows的CAS

在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions

InterlockedCompareExchange ( __inout LONG volatile *Target,
                                __in LONG Exchange,
                                __in LONG Comperand);

3) C++11中的CAS

C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library

   
template< class T >
bool atomic_compare_exchange_weak( std::atomic<T>* obj,
                                   T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic<T>* obj,
                                   T* expected, T desired );

2.实现无锁的栈

首先写一个一般的栈:
//一般的栈。
  
  typedef ELEM int;
  #define MAX (2048)
  
  static ELEM Stack[MAX];
  static int top = 0;
  
  bool Push(const ELEM& val)
  {
      if (top >= MAX) return false;
  
      Stack[top] = val;
      ++top;
      return true;
  }
  
  
  bool Pop(ELEM& val)
  {
      if (top == 0) return false;
  
      --top;
      val = Stack[top];
      
      return true;
  }

这样的栈在单线程场合下是常见的,也很简洁明了,但它却不适用于多线程的场合,试想一下,如果两个线程,线程a,线程b, 同一时间对同一个栈进行Push操作,参考上面的代码,假设此时top = 0, 如果线程a在执行到第13行时停了下来,切换到线程b进行Push,线程b执行完13行,但没有执行14行的时候,这时Stack[top]中已经插入了线程b要插入的值,但top还没更新,如果这时线程b不幸又被切换了出去,换到线程a继续执行,那么线程a又会在同样一个位置top = 0的地方插入,从而破坏了线程b的操作!

   我们可以观察到,上面的代码在多线程下之所以不安全,是因为Stack被多个线程同时修改,但各个线程又没有对关键的变量在访问顺序上作保护。对此,我们可以引入一些同步的机制来修改它,使得它能在多线程的场合里是操作安全的。

下面写一个有锁的栈:
//带锁的栈。
  
  typedef ELEM int;
  #define MAX (2048)
  
  static ELEM Stack[MAX];
  static int top = 0;
  
  static Mutex mutex;
  
  bool Push(ELEM val)
  {
      if (top >= MAX) return false;
  
      Lock(&mutex);
 
      Stack[top] = val;
      ++top;
      
      Unlock(&mutex);
 
      return true;
  }
  
  
  bool Pop(ELEM& val)
  {
      if (top == 0) return false;
  
      Lock(&mutex);
 
      --top;
      val = Stack[top];
        
      Unlock(&mutex);
      return true;
  }

   上面的代码就是我们常说的有锁操作了,mutex保证了各个线程对公共变量的访问是安全的,各个线程在同时对Stack进行操作时,需要先抢占mutex,抢到就可以对stack进行操作,没抢到就先等着。这里付出了些代价,但保证了操作的安全可靠性。

   那么这些保护是有必要的吗?再观察一下前面的代码,多个线程有可能,有需要同时修改的变量就一个而已:top. 只要我们参保证top在多线程的环境里能够安全地被修改,那对整个stack的修改也都是安全的。

   事情看起来,好像比较简单。

   要保证对top变量的原子操作,我们需要cpu提供一些特殊的支持,来保证我们在对某些内存进行修改时,不会被线程所中断,它要么就完成,要么就不完成,而不会在完成到一半时,被别的线程中断。

   在intel平台上,从80486开始,CMPXCHG汇编指令可以帮助我们完全这件事情,这就是我们通常所说CAS操作的基础。

   下面我们尝试用cas来写一个无锁的stack.

//无锁的栈。
  
  typedef ELEM int;
  #define MAX (2048)
  
  static ELEM Stack[MAX];
  static int top = 0;
  
  bool Push(ELEM val)
  {
      int old_top;
      
      do
      {
         old_top = top;
         if (old_top >= MAX) return false;
         
         if (cas(&top, old_top, old_top + 1)) 
             break;
 
       }while(1);
   
      Stack[old_top] = val;
      
      return true;
  }
  
  
  bool Pop(ELEM& val)
  {
      int old_top;
      do
      {
          old_top = top;
      
          if (old_top == 0) return false;
          
          val = Stack[old_top - 1];
 
          if (cas(&top, old_top, old_top - 1))
               break;
 
       } while(1);
 
 
      return true;
  }

参考资料:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值