比較並交換(compare and swap, CAS),是原子操做的一種,可用於在多線程編程中實現不被打斷的數據交換操做,從而避免多線程同時改寫某一數據時因爲執行順序不肯定性以及中斷的不可預知性產生的數據不一致問題。 該操做經過將內存中的值與指定數據進行比較,當數值同樣時將內存中的數據替換爲新的值。php
一個CAS操做的過程能夠用如下c代碼表示: [1]編程
1 int cas(long *addr, long old, long new) 2 { 3 /* Executes atomically. */ 4 if(*addr != old) 5 return 0; 6 *addr = new; 7 return 1; 8 }
在使用上,一般會記錄下某塊內存中的舊值,經過對舊值進行一系列的操做後獲得新值,而後經過CAS操做將新值與舊值進行交換。若是這塊內存的值在這期間內沒被修改過,則舊值會與內存中的數據相同,這時CAS操做將會成功執行 使內存中的數據變爲新值。若是內存中的值在這期間內被修改過,則通常[2]來講舊值會與內存中的數據不一樣,這時CAS操做將會失敗,新值將不會被寫入內存。數據結構
在應用中CAS能夠用於實現無鎖數據結構,常見的有無鎖隊列(先入先出)[3] 以及無鎖堆(先入後出)。對於可在任意位置插入數據的鏈表以及雙向鏈表,實現無鎖操做的難度較大。[4]。多線程
ABA問題是無鎖結構實現中常見的一種問題,可基本表述爲:函數
對於P1來講,數值A未發生過改變,但實際上A已經被變化過了,繼續使用可能會出現問題。在CAS操做中,因爲比較的可能是指針,這個問題將會變得更加嚴重。試想以下狀況:atom
top | V 0x0014 | Node A | --> | Node X | --> ……
有一個堆(先入後出)中有top和節點A,節點A目前位於堆頂top指針指向A。如今有一個進程P1想要pop一個節點,所以按照以下無鎖操做進行spa
pop() { do{ ptr = top; // ptr = top = NodeA next_prt = top->next; // next_ptr = NodeX } while(CAS(top, ptr, next_ptr) != true); return ptr; }
而進程P2在執行CAS操做以前打斷了P1,並對堆進行了一系列的pop和push操做,使堆變爲以下結構:線程
top | V 0x0014 | Node C | --> | Node B | --> | Node X | --> ……
進程P2首先pop出NodeA,以後又Push了兩個NodeB和C,因爲內存管理機制中普遍使用的內存重用機制,致使NodeC的地址與以前的NodeA一致。指針
這時P1又開始繼續運行,在執行CAS操做時,因爲top依舊指向的是NodeA的地址(實際上已經變爲NodeC),所以將top的值修改成了NodeX,這時堆結構以下:隊列
top | 0x0014 V | Node C | --> | Node B | --> | Node X | --> ……
通過CAS操做後,top指針錯誤的指向了NodeX而不是NodeB。
CAS操做基於CPU提供的原子操做指令實現。對於Intel X86處理器,可經過在彙編指令前增長LOCK前綴來鎖定系統總線,使系統總線在彙編指令執行時沒法訪問相應的內存地址。而各個編譯器根據這個特色實現了各自的原子操做函數。[5]