在前面我们已经讲了如何使用 channel 在多个 goroutine 之间进行通信,其实对于并发还有一种较为常用通信方式,那就是共享内存。

首先我们来看一个例子:

  1. package main
  2. import (
  3. "log"
  4. "time"
  5. )
  6. var name string
  7. func main() {
  8. name = "小明"
  9. go printName()
  10. go printName()
  11. time.Sleep(time.Second)
  12. name = "小红"
  13. go printName()
  14. go printName()
  15. time.Sleep(time.Second)
  16. }
  17. func printName() {
  18. log.Println("name is", name)
  19. }

运行程序,可以得到类似输出结果:

  1. 2018/03/23 14:53:28 name is 小明
  2. 2018/03/23 14:53:28 name is 小明
  3. 2018/03/23 14:53:29 name is 小红
  4. 2018/03/23 14:53:29 name is 小红

可以看到在两个 goroutine 中我们都可以访问 name 这个变量,当修改它后,在不同的 goroutine 中都可以同时获取到最新的值。

这就是一个最简单的通过共享变量(内存)的方式在多个 goroutine 进行通信的方式。

下面再来看一个例子:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func main() {
  7. var (
  8. wg sync.WaitGroup
  9. numbers []int
  10. )
  11. for i := 0; i < 10; i++ {
  12. wg.Add(1)
  13. go func(i int) {
  14. numbers = append(numbers, i)
  15. wg.Done()
  16. }(i)
  17. }
  18. wg.Wait()
  19. fmt.Println("The numbers is", numbers)
  20. }

多次运行代码,可以得到类似输出:

  1. The numbers is [0 1 5 4 7]
  2. The numbers is [0 5 7]

可以看到当我们并发对同一个切片进行写操作的时候,会出现数据不一致的问题,这就是一个典型的共享变量的问题。

针对这个问题我们可以使用 Lock(锁)来修复,从而保证数据的一致性,例如:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func main() {
  7. var (
  8. wg sync.WaitGroup
  9. numbers []int
  10. mux sync.Mutex
  11. )
  12. for i := 0; i < 10; i++ {
  13. wg.Add(1)
  14. go func(i int) {
  15. mux.Lock()
  16. numbers = append(numbers, i)
  17. mux.Unlock()
  18. wg.Done()
  19. }(i)
  20. }
  21. wg.Wait()
  22. fmt.Println("The numbers is", numbers)
  23. }

修改过后,我们再次运行代码,可以看到最后的 numbers 都会包含 0~9 这个10个数字。

sync.Mutex 是互斥锁,只有一个信号标量;在 Go 中还有一种读写锁 sync.RWMutex,对于我们的共享对象,如果可以分离出读和写两个互斥信号的情况,可以考虑使用它来提高读的并发性能。

例如代码:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. )
  8. func main() {
  9. var (
  10. mux sync.Mutex
  11. state1 = map[string]int{
  12. "a": 65,
  13. }
  14. muxTotal uint64
  15. rw sync.RWMutex
  16. state2 = map[string]int{
  17. "a": 65,
  18. }
  19. rwTotal uint64
  20. )
  21. for i := 0; i < 10; i++ {
  22. go func() {
  23. for {
  24. mux.Lock()
  25. _ = state1["a"]
  26. mux.Unlock()
  27. atomic.AddUint64(&muxTotal, 1)
  28. }
  29. }()
  30. }
  31. for i := 0; i < 10; i++ {
  32. go func() {
  33. for {
  34. rw.RLock()
  35. _ = state2["a"]
  36. rw.RUnlock()
  37. atomic.AddUint64(&rwTotal, 1)
  38. }
  39. }()
  40. }
  41. time.Sleep(time.Second)
  42. fmt.Println("sync.Mutex readOps is", muxTotal)
  43. fmt.Println("sync.RWMutex readOps is", rwTotal)
  44. }

运行代码可以得到如下结果:

  1. sync.Mutex readOps is 1561870
  2. sync.RWMutex readOps is 15651069

可以看到使用 sync.RWMutex 的读的并发能力大概是 sync.Mutex 的十倍,从而大大提高了其并发能力。

总结:

  • 我们可以通过共享内存的方式实现多个 goroutine 中的通信。
  • 多个 goroutine 对于共享的内存进行写操作的时候,可以使用 Lock 来避免数据不一致的情况。
  • 对于可以分离为读写操作的共享数据可以考虑使用 sync.RWMutex 来提高其读的并发能力。