您的位置:首页 > 其它

CAS的小实验

2016-07-10 21:36 351 查看
CAS是什么?

cas是一种在多线程的编程中的一种避免竞争态的方法,作用类似与互斥锁。当多个线程对一个共享的数据进行访问的时候,如果多个线程同时对这个数据进行写,而没有采取任何方法避免多线程同时写,数据就会出现各种问题。

解决这个问题的最经典的方法就是使用互斥锁了。一个线程需要对那个数据进行访问之前,就先对那个数据上锁,而互斥锁只能被一个实体(线程)获取,在获取互斥锁的线程释放那个锁之前,其他线程如果尝试获取那个锁,就会陷入休眠。这样子,保证只有一个线程能够操纵那个数据,就不会有问题了。互斥锁属于悲观锁,就是它会假设对数据的操作会出问题,所以作出最坏的心里准备。但是,锁的操作比较费时,这涉及操作系统比较底层的事情,例如,对锁的操作涉及上下文的切换等。

cas属于乐观锁,即对于数据的操纵不会作出太坏的假设。cas的本质是,先获取数据在内存中的一个旧的值oldValue,然后对这个数据进行修改的时候,先用oldValue与当前内存中的数据值newValue进行对比,如果oldValue == newValue,就把想要对这个数据的修改施加到这个数据身上。否则,在重新进行整个cas的过程。

举个例子,当前有多个线程,有一个变量a。多个线程同时对a做a=a+1的操作。假设只有两个线程b和c。先描述一下没有cas的情况下,a的可能的“命运””。(按照事件的发送顺序)

1. b线程取出a的值

        2.b线程计算a+1的值,保存在一个临时变量tmp中

3.c线程取出a的值

4.c线程计算a+1的值,保存在一个临时变量tmp1中

5.b线程将tmp的值赋给a

6.c线程把tmp1的值赋给a

可以发现,其实在步骤5和步骤6之后,a的值是一样的。因为步骤2和4分别在b和c线程中存了一份a+1的副本,然后在步骤5和6中分别将这个副本的值写到a中,步骤5中的操作的结果被覆盖了

这个就是多线程中如果不进行保护措施的时候会发生的问题。那么cas是怎么进行对数据的保护的呢?举同样的例子。下面列出a的“命运”

1.b线程取出a的值,存到oldValue中。

2.b线程计算出oldValue+1的值,存到newValue中。

3.c线程取出a的值,存到oldValue1中。

4.c线程计算oldValue+1的值,存到newValue1。

5.b线程用oldValue与a所在的地址中的值*(&a)进行比较,发现oldValue == *(&a)相等,就把newValue赋给a,即a=newValue。

6.c线程用oldValue1与a所在的地址中的值*(&a)进行比较,发现oldValue != *(&a),因为 *(&a)已经被b线程写了。此时,c线程重新进行整个过程,即先取出a中的值到oldValue2,计算a+1的值存到newValue2,将oldValue2与*(&a)进行比较,发现相等(如果没有其他线程对a进行操作),把newValue2写到a中。

这样,就不会出现写操作被覆盖的问题了。同时,也没有使用了锁。

cas在gcc中有内置的原子操作支持:__sync_val_compare_and_swap和__sync_bool_compare_and_swap,这两个主要是返回值的不同。原子操作就是,将oldValue与a的值进行比较的操作和将newValue写入a的操作合并为“一体”,要么两个操作都完成,要么都没有完成,而不会在进行比较之后a的值被其他线程修改。

下面是一个代码的例子:
#include <cstdio>
#include <pthread.h>
#include <unistd.h>

void* testCAS(void * op)
{
int *tmp = (int*)op;
int toTest = *tmp;
usleep(5);//睡眠,使得*tmp可以被其他线程进行修改
while(!__sync_bool_compare_and_swap(tmp,toTest,toTest+1))//cas操作
{
toTest = *tmp;
printf("sync fail\n");
}

return NULL;
}

int main()
{
pthread_t threads[10];
int toTest = 0;
for(int i=0;i<10;i++)
{
pthread_create(&threads[i],NULL,testCAS,&toTest);
}
for(int i=0;i<10;i++)
{
if(pthread_join(threads[i],NULL)<0)
{
printf("pthread_join threads[%d] fail\n",i);
}
}
printf("the final toTest: %d\n",toTest);

}

CAS的应用

其实,基本有多线程的情况下,基本都可以或多或少的使用cas来对数据进行保护。

我最近知道的一个应用场景是使用CAS来实现无锁队列。无锁队列就是多线程对一个队列进行出队和入队的操作。如果队列使用了链表来实现,就会涉及到头指针和尾指针的修改。这两个指针的值需要应用cas来防止多个线程同时进行修改。如果队列使用数组来实现,那么记录队列的起始位置和末尾位置,这个也是需要用cas来进行保护的。前面说到,相对于使用锁,cas速度更快。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  CAS 多线程