天天看點

Go 分布式學習利器(18)-- Go并發程式設計之lock+WaitGroup實作線程安全

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)      

繼續閱讀