CAS 无锁实现
为了实现多资源争用就引入了锁,但是锁的性能并不是那么好。一方面通过调整锁的粒度来优化,一方面提出了无锁的方式 – CAS。——Compare & Set,或是 Compare & Swap。
必须要明确的是这需要CPU指令的支持。在正常逻辑视角来看,对比,交换。是两个步骤,但是通过CPU指令集的优化可以把CAS作为原子操作。
代码
看一看内存*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吧)
- Fetch And Add,一般用来对变量做 +1 的原子操作
- Test-and-set,写值到某个内存位置并传回其旧值。汇编指令BST
- Test and Test-and-set,用来低低Test-and-Set的资源争夺情况
进阶一 无锁队列
队列常常是使用在池技术上,比如进程池,线程池,连接池等等。在多并发操作上获取资源就需要对队列加锁,这边就是以CAS去实现资源争用。
入队
进队列用CAS实现的方式:
入队分为三个步骤。
- 创建新节点。
- 新节点加入到原有尾节点后。
- 尾节点指向新节点。
在这里,当多个线程(实例)向队列追加时,新节点加入到尾节点这个操作错在争用,需要使用CAS。代码如下。
EnQueue(x) //进队列
{
//准备新加入的结点数据
q = new record();
q->value = x;
q->next = NULL;
do {
p = tail; //取链表尾指针的快照
} while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链在尾指针上,再试
// 线程在这里异常退出
CAS(tail, p, q); //置尾结点
}
以上代码还有一个风险:
如果T1 线程在使用CAS更新tail 指针之前gg了。这回导致其他线程检测 CAS(p->next, NULL, q) 永远为true。进入死循环。
优化代码:
EnQueue(x) //进队列改良版
{
q = new record();
q->value = x;
q->next = NULL;
p = tail;
oldp = p
do {
while (p->next != NULL)
p = p->next;
} while( CAS(p.next, NULL, q) != TRUE); //如果没有把结点链在尾上,再试
CAS(tail, oldp, q); //置尾结点
}
###出队
出队代码相对简单:
DeQueue() //出队列
{
do{
p = head;
if (p->next == NULL){
return ERR_EMPTY_QUEUE;
}
while( CAS(head, p, p->next) != TRUE );
return p->next->value;
}
进程 ABA 问题
简答的说就是 A读取了某个状态后被B抢占了,然后B进行了一系列骚操作之后值并没有改变。A毫无感知,继续执行了。
若果CAS 保存的是指针在这里就会出问题。
你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。
解决ABA的问题
维基百科上给了一个解——使用double-CAS(双保险的CAS),例如,在32位系统上,我们要检查64位的内容
1)一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。
2)只有这两个都一样,才算通过检查,要吧赋新的值。并把计数器累加1。
这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)
代码:
SafeRead(q)
{
loop:
p = q->next;
if (p == NULL){
return p;
}
Fetch&Add(p->refcnt, 1);
if (p == q->next){
return p;
}else{
Release(p);
}
goto loop;
}