Go 互斥

上面的例子中,我们看过了如何在多个协程之间原子地访问计数器,对于更复杂的例子,我们可以使用Mutex来在多个协程之间安全地访问数据。

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. "runtime"
  6. "sync"
  7. "sync/atomic"
  8. "time"
  9. )
  10. func main() {
  11. // 这个例子的状态就是一个map
  12. var state = make(map[int]int)
  13. // 这个`mutex`将同步对状态的访问
  14. var mutex = &sync.Mutex{}
  15. // ops将对状态的操作进行计数
  16. var ops int64 = 0
  17. // 这里我们启动100个协程来不断地读取这个状态
  18. for r := 0; r < 100; r++ {
  19. go func() {
  20. total := 0
  21. for {
  22. // 对于每次读取,我们选取一个key来访问,
  23. // mutex的`Lock`函数用来保证对状态的
  24. // 唯一性访问,访问结束后,使用`Unlock`
  25. // 来解锁,然后增加ops计数器
  26. key := rand.Intn(5)
  27. mutex.Lock()
  28. total += state[key]
  29. mutex.Unlock()
  30. atomic.AddInt64(&ops, 1)
  31. // 为了保证这个协程不会让调度器出于饥饿状态,
  32. // 我们显式地使用`runtime.Gosched`释放了资源
  33. // 控制权,这种控制权会在通道操作结束或者
  34. // time.Sleep结束后自动释放。但是这里我们需要
  35. // 手动地释放资源控制权
  36. runtime.Gosched()
  37. }
  38. }()
  39. }
  40. // 同样我们使用10个协程来模拟写状态
  41. for w := 0; w < 10; w++ {
  42. go func() {
  43. for {
  44. key := rand.Intn(5)
  45. val := rand.Intn(100)
  46. mutex.Lock()
  47. state[key] = val
  48. mutex.Unlock()
  49. atomic.AddInt64(&ops, 1)
  50. runtime.Gosched()
  51. }
  52. }()
  53. }
  54. // 主协程Sleep,让那10个协程能够运行一段时间
  55. time.Sleep(time.Second)
  56. // 输出总操作次数
  57. opsFinal := atomic.LoadInt64(&ops)
  58. fmt.Println("ops:", opsFinal)
  59. // 最后锁定并输出状态
  60. mutex.Lock()
  61. fmt.Println("state:", state)
  62. mutex.Unlock()
  63. }

运行结果

  1. ops: 3931611
  2. state: map[0:84 2:20 3:18 1:65 4:31]