標籤:atomic cas 原子操作 go語言 go並發編程
atomic是最輕量級的鎖,在一些情境下直接使用atomic包還是很有效。
下面內容摘秒自《GO並發編程實戰》—— 原子操作:
CAS操作的優勢是,可以在不形成臨界區和建立互斥量的情況下完成並發安全的值替換操作。
這可以大大的減少同步對程式效能的損耗。
當然,CAS操作也有劣勢。在被操作值被頻繁變更的情況下,CAS操作並不那麼容易成功。
原子操作共有5種,即:增或減、比較並交換、載入、儲存和交換
1. 增或減
被用於進行增或減的原子操作(以下簡稱原子增/減操作)的函數名稱都以“Add”為首碼,並後跟針對的具體類型的名稱。
不過,由於atomic.AddUint32函數和atomic.AddUint64函數的第二個參數的類型分別是uint32和uint64,所以我們無法通過傳遞一個負的數值來減小被操作值。
atomic.AddUint32(&ui32, ^uint32(-NN-1)) 其中NN代表了一個負整數
2. 比較並交換
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
第一個參數的值應該是指向被操作值的指標值。該值的類型即為*int32。
後兩個參數的類型都是int32類型。它們的值應該分別代表被操作值的舊值和新值
CompareAndSwapInt32函數在被調用之後會先判斷參數addr指向的被操作值與參數old的值是否相等。
僅當此判斷得到肯定的結果之後,該函數才會用參數new代表的新值替換掉原先的舊值。否則,後面的替換操作就會被忽略。
3. 載入
v := atomic.LoadInt32(&value)
函數atomic.LoadInt32接受一個*int32類型的指標值,並會返回該指標值指向的那個值
有了“原子的”這個形容詞就意味著,在這裡讀取value的值的同時,當前電腦中的任何CPU都不會進行其它的針對此值的讀或寫操作。
這樣的約束是受到底層硬體的支援的。
4. 儲存
在原子的儲存某個值的過程中,任何CPU都不會進行針對同一個值的讀或寫操作。
如果我們把所有針對此值的寫操作都改為原子操作,那麼就不會出現針對此值的讀操作因被並發的進行而讀到修改了一半的值的情況了。
原子的值儲存操作總會成功,因為它並不會關心被操作值的舊值是什麼。
函數atomic.StoreInt32會接受兩個參數。第一個參數的類型是*int 32類型的,其含義同樣是指向被操作值的指標。而第二個參數則是int32類型的,它的值應該代表欲儲存的新值。其它的同類函數也會有類似的參數聲明列表。
5. 交換
與CAS操作不同,原子交換操作不會關心被操作值的舊值。它會直接設定新值。但它又比原子載入操作多做了一步。作為交換,它會返回被操作值的舊值。此類操作比CAS操作的約束更少,同時又比原子載入操作的功能更強。
以atomic.SwapInt32函數為例。它接受兩個參數。第一個參數是代表了被操作值的記憶體位址的*int32類型值,而第二個參數則被用來表示新值。注意,該函數是有結果值的。該值即是被新值替換掉的舊值。atomic.SwapInt32函數被調用後,會把第二個參數值置於第一個參數值所表示的記憶體位址上(即修改被操作值),並將之前在該地址上的那個值作為結果返回。
例子:
df.rmutex.Lock()
defer df.rmutex.Unlock()
return df.roffset / int64(df.dataLen)
我們現在去掉施加在上面的鎖定和解鎖操作,轉而使用原子操作來實現它。修改後的代碼如下:
offset := atomic.LoadInt64(&df.roffset)
return offset / int64(df.dataLen)
用原子操作來替換mutex鎖
其主要原因是,原子操作由底層硬體支援,而鎖則由作業系統提供的API實現。若實現相同的功能,前者通常會更有效率。
關於atomic,並發編程的作者說很細很清楚,再可以看看下面兩篇好文檔::
Golang 1.3 sync.Mutex 源碼解析
Golang 1.3 sync.Atomic源碼解析
MAIL: [email protected]
BLOG: http://blog.csdn.net/xcl168
Go語言atomic原子操作