天天看点

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)      

继续阅读