實現無鎖隊列的關鍵點有二:
1、各個平臺的原子操作或者說CAS原語;
2、ABA問題的理解和解決。
首先來說說這個CAS原語,所謂CAS(Compare And Swap)即比較並交換,在 Intel 處理器中,比較並交換通過指令的 cmpxchg 系列實現。CAS有三個操作數: 內存位置(V)、預期原值(A)和新值(B)。如果內存位置V的值與預期A原值相匹配,那麼處理器會自動將該位置值更新爲新值B。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了“我認爲位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”CAS的C語言實現如下:
inline bool CAS2(pointer_t *addr, pointer_t &old_value, pointer_t &new_value)
{
bool ret;
__asm__ __volatile__(
"lock cmpxchg16b %1;\n"
"sete %0;\n"
:"=m"(ret),"+m" (*(volatile pointer_t *) (addr))
:"a" (old_value.ptr), "d" (old_value.tag), "b" (new_value.ptr), "c" (new_value.tag));
return ret;
}
這其中包括了內聯彙編代碼(這個可以去看看AT&T彙編語法),其中能夠支持多線程的並行安全執行的祕訣就在 "lock cmpxchg16b %1;\n"這句中的“lock”,這個lock就和我們基本多線程編程中的鎖相當,只不過,這個lock不再是普通的鎖,它鎖的是地址總線,在多核編程情景下,當已經有CPU的線程A已經訪問某地址時,這時地址總線會被鎖定,其他CPU核心的線程B無法再訪問當前地址,直到A訪問完畢之後,其他線程如B纔可能訪問該地址,這樣就保證了線程A訪問該地址的原子性操作。
再來講講這個ABA問題。在進行CAS操作時,因爲在更改V值之前,CAS主要是通過訪問V的值是否仍然和A相等,所以,在第一次讀取V以及對V執行CAS操作之前,如果有其他線程將V的值先從A改爲B,而另外的線程又將V的值從B改回了A,這樣會使CAS算法混亂。顯然,在這種情況下,CAS的操作會成功,這類的問題稱爲ABA問題。要解決這類問題,就是不再重用A,通常的做法是用標記或版本編號與進行CAS操作的每個值相關聯,並原子的更新值和標記。
CAS實現的無鎖
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include "timer.h"
int mutex = 0;
int lock = 0;
int unlock = 1;
static volatile int count = 0;
void *test_func(void *arg)
{
int i = 0;
for(i = 0; i < 2000000; i++)
{
while (!(__sync_bool_compare_and_swap (&mutex,lock, 1) ))usleep(100000);
count++;
__sync_bool_compare_and_swap (&mutex, unlock, 0);
}
return NULL;
}
int main(int argc, const char *argv[])
{
Timer timer;
timer.Start();
pthread_t thread_ids[10];
int i = 0;
for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
{
pthread_create(&thread_ids[i], NULL, test_func, NULL);
}
for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++)
{
pthread_join(thread_ids[i], NULL);
}
timer.Stop();
timer.Cost_time();
printf("結果:count = %d\n",count);
return 0;
}