Go語言中通過Groutine 啟動一個Go協程,不同協程之間是并發執行的,就像C++/Java中線程之間線程安全是一個常見的問題。
如下Go 語言代碼:
func TestConcurrent(t *testing.T) {
var counter int = 0
for i := 0;i < 5000; i ++{
go func() { // 啟動groutine 進行變量的自增
counter ++
}()
}
time.Sleep(time.Second * 1)
t.Logf("Counter = %d", counter)
}
最後的結果輸出如下:
=== RUN TestConcurrent
concurrent_test.go:18: Counter = 4663
--- PASS: TestConcurrent (1.00s)
希望變量
Counter
的結果是5000,最終因為協程之間并發通路共享記憶體的原因導緻部分協程執行的結果沒有得到變更,進而資料不符合預期。
Go中提供了
Mutex
和
RWMutex
,來實作鎖功能。前者就是正常的Lock, Unlock;後者提供了讀寫鎖,在協程讀寫分離中提供了讀讀互不影響,讀寫互斥。
如下測試代碼:
func TestMutex(t *testing.T) {
var counter int = 0
// var mu sync.Mutex // mutex lock
var mu sync.RWMutex // rwmutex lock
for i := 0;i < 5000; i ++{
go func() {
// defer mutex unlock
// 如果協程異常退出,能夠釋放鎖
defer func() {
mu.Unlock()
}()
mu.Lock()
counter ++
}()
}
time.Sleep(time.Second * 1)
t.Logf("Counter = %d", counter)
}
最終輸出如下,通過mutex 實作了變量通路的安全:
=== RUN TestMutex
concurrent_test.go:36: Counter = 5000
--- PASS: TestMutex (1.01s)
可以看到如上代碼需要通過Sleep來讓所有的協程都執行完,顯然不合理。、
func TestWaitGroup(t *testing.T) {
var counter int = 0
var mu sync.RWMutex // rw lock, 讀寫互斥
var wg sync.WaitGroup
for i := 0;i < 5000; i ++{
wg.Add(1)
go func() {
defer func() {
mu.Unlock()
}()
mu.Lock()
counter ++
wg.Done() // wg.Add(-1)
}()
}
wg.Wait()
t.Logf("Counter = %d", counter)
}
=== RUN TestWaitGroup
concurrent_test.go:57: Counter = 5000
--- PASS: TestWaitGroup (0.00s)