當同時存在讀寫執行緒時,預設情況下是不保證執行緒安全的,因而需要利用訊號量來進行執行緒同步(Synchronization),如關鍵程式碼段、互斥體等,同時作業系統也提供了相應的API。然而同步並不總是滿足條件的且有效率的,比如陷入核心時會有效能損失、死鎖、活鎖以及資源浪費等。
於是Lock-Free和Wait-Free的思想出現了,由於此時不存在讀寫執行緒的同步,因而在寫執行緒執行時,讀執行緒也在執行(多核中兩個執行緒在不同的核上被排程執行),而且程式碼量減少,程式執行更快。而這一思想是通過CAS機制來實現,如下
1 2 3 4 5 6 7 8 |
template<typename T> bool CAS(T* ptr, T expected, T fresh) { if(*ptr != expected) return false; *ptr = fresh; return true; } |
CAS的原理是,將舊值與一個期望值進行比較,如果相等,則更新舊值,型別T = {char, short, int, __int64, …}等,以及指標(pointer to any type)。
注意CAS這裡只是說明了原理,並不是真實的原始碼實現,具體實現請參考作業系統。
在Windows API中,提供了很多原子操作(Atomic Operatoration),如InterlockedCompareExchange等一系列InterLocked函式,從彙編的角度來講,intel的XCHG指令即可以一個時鐘週期內完成資料的交換(暫存器和記憶體的資料交換),使用方法可參考InterlockedCompareExchange的反彙編程式碼。
考慮這樣一種情況:存在多個讀執行緒和一個寫執行緒,在使用同步方法時,很可能寫執行緒並不能立即獲得鎖,最壞的情況下是寫執行緒永遠得不到鎖,即進入活鎖狀態。但是使用CAS的方法時,便可以讓讀寫執行緒並行執行,當寫執行緒一旦更新為新的共享資料時,讀執行緒便能即時讀出更新後的資料。
如下示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Widget { Data* p_; ... void Use() { ... use p_ ... } void Update() { Data * pOld, * pNew = new Data; do { pOld = p_; ... }while (!CAS(&p_, pOld, pNew)); } }; |
但隨之而來會有一個疑問,Update函式中該何時刪除舊資料呢,由於很有可能有別的讀執行緒在使用舊資料。對於JAVA等有自動記憶體回收(GC)機制的語言環境而言,這不是問題,但對於C/C++等無GC機制的環境而言,舊資料的回收就比較棘手的問題了。
當然也存在很多的解決方法,這也成為CAS機制中最有趣最受討論的問題,而且在不同條件下方法也不同,這裡便不一一贅述了,具體可以檢視“參考文獻”部分的論文。
參考文獻: