一、前言
go語言類似Java JUC包也提供了一些列用於多線程之間進行同步的措施,比如低級的同步措施有 鎖、CAS、原子變量操作類。相比Java來說go提供了獨特的基於通道的同步措施。本節我們先來看看go中CAS操作
二、CAS操作
go中的Cas操作與java中類似,都是借用了CPU提供的原子性指令來實現。CAS操作修改共享變量時候不需要對共享變量加鎖,而是通過類似樂觀鎖的方式進行檢查,本質還是不斷的佔用CPU 資源換取加鎖帶來的開銷(比如上下文切換開銷)。下面一個例子使用CAS來實現計數器
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var (
counter int32 //計數器
wg sync.WaitGroup //信號量
)
func main() {
threadNum := 5
//1. 五個信號量
wg.Add(threadNum)
//2.開啓5個線程
for i := 0; i < threadNum; i++ {
go incCounter(i)
}
//3.等待子線程結束
wg.Wait()
fmt.Println(counter)
}
func incCounter(index int) {
defer wg.Done()
spinNum := 0
for {
//2.1原子操作
old := counter
ok := atomic.CompareAndSwapInt32(&counter, old, old+1)
if ok {
break
} else {
spinNum++
}
}
fmt.Printf("thread,%d,spinnum,%d\n",index,spinNum)
}
- 如上代碼main線程首先創建了5個信號量,然後開啓五個線程執行incCounter方法
- incCounter內部執行代碼2.1 使用cas操作遞增counter的值, atomic.CompareAndSwapInt32具有三個參數,第一個是變量的地址,第二個是變量當前值,第三個是要修改變量爲多少,該函數如果發現傳遞的old值等於當前變量的值,則使用第三個變量替換變量的值並返回true,否則返回false。
- 這裏之所以使用無限循環是因爲在高併發下每個線程執行CAS並不是每次都成功,失敗了的線程需要重寫獲取變量當前的值,然後重新執行CAS操作。讀者可以把線程數改爲10000或者更多會發現輸出thread,5329,spinnum,1其中1說明該線程嘗試了兩個CAS操作,第二次才成功。
三、總結
go中CAS操作具有原子性,在解決多線程操作共享變量安全上可以有效的減少使用鎖所帶來的開銷,但是這是使用cpu資源做交換的。