無鎖編程 / lock-free / 非阻塞同步
無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫
非阻塞同步(Non-blocking Synchronization)。
實現非阻塞同步的方案稱為“無鎖編程算法”(
Non-blocking algorithm)。
lock-free是目前最常見的無鎖編程的
實現級別(一共三種級別)。
為什么要 Non-blocking sync ?
使用lock實現線程同步有很多缺點:
* 產生競爭時,線程被阻塞等待,無法做到線程實時響應。
* dead lock。
* live lock。
* 優先級翻轉。
* 使用不當,造成性能下降。
如果在不使用 lock 的情況下,實現變量同步,那就會避免很多問題。雖然目前來看,無鎖編程並不能替代 lock。
實現級別
非同步阻塞的實現可以分成三個級別:wait-free/lock-free/obstruction-free。
wait-free
是最理想的模式,整個操作保證每個線程在有限步驟下完成。
保證系統級吞吐(system-wide throughput)以及無線程飢餓。
截止2011年,沒有多少具體的實現。即使實現了,也需要依賴於具體CPU。
lock-free
允許個別線程飢餓,但保證系統級吞吐。
確保至少有一個線程能夠繼續執行。
wait-free的算法必定也是lock-free的。
obstruction-free
在任何時間點,一個線程被隔離為一個事務進行執行(其他線程suspended),並且在有限步驟內完成。在執行過程中,一旦發現數據被修改(采用時間戳、版本號),則回滾。
也叫做樂觀鎖,即
樂觀並發控制(OOC)。事務的過程是:1讀取,並寫時間戳;2准備寫入,版本校驗;3校驗通過則寫入,校驗不通過,則回滾。
lock-free必定是obstruction-free的。
CAS原語
LL/SC, atom read-modify-write
如果CPU提供了Load-Link/Store-Conditional(LL/SC)
這對指令,則就可以輕松實現變量的CPU級別無鎖同步。
LL [addr],dst:從內存[addr]處讀取值到dst。
SC value,[addr]:對於當前線程,自從上次的LL動作后內存值沒有改變,就更新成新值。
上述過程就是實現lock-free的 read-modify-write 的原子操作。
CAS (Compare-And-Swap)
LL/SC這對CPU指令沒有實現,那么就需要尋找其他算法,比如CAS。
CAS是一組原語指令,用來實現多線程下的變量同步。
在 x86 下的指令CMPXCHG實現了CAS,前置LOCK既可以達到原子性操作。截止2013,大部分多核處理器均支持CAS。
CAS原語有三個參數,內存地址,期望值,新值。如果內存地址的值==期望值,表示該值未修改,此時可以修改成新值。否則表示修改失敗,返回false,由用戶決定后續操作。
Bool CAS(T* addr, T expected, T newValue) { if( *addr == expected ) { *addr = newValue; return true; } else return false; }
ABA 問題
thread1意圖對val=1進行操作變成2,cas(*val,1,2)。
thread1先讀取val=1;thread1被
搶占(preempted),讓thread2運行。
thread2 修改val=3,又修改回1。
thread1繼續執行,發現期望值與“原值”(其實被修改過了)相同,完成CAS操作。
使用CAS會造成ABA問題,特別是在使用指針操作一些並發數據結構時。
解決方案
ABAʹ:添加額外的標記用來指示是否被修改。
語言實現
Java demo
AtomicInteger atom = new AtomicInteger(1);
boolean r = atom.compareAndSet(1, 2);
C# demo
int i=1;
Interlocked.Increment(ref i);
Refs
http://en.wikipedia.org/wiki/ABA_problem ABA 以及相關例子
