Go 并不保证在多个 goroutine 中修改同块数据(critical section)时不会出现竞争条件(race condition):
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup) {
x = x + 1
wg.Done()
}
func main() {
var w sync.WaitGroup
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w)
}
w.Wait()
fmt.Println("final value of x", x)
}
final value of x 957
看起来 x = x + 1
执行了 1000 次,但值只有 957。
Go 在 sync
包中提供了基础的同步原语(synchronization primitives),里面有一些传统多线程编程中经常使用的工具,比如锁(Mutex)。用 Mutex 解决上面的问题:
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup, m *sync.Mutex) {
m.Lock()
x = x + 1
m.Unlock()
wg.Done()
}
func main() {
var w sync.WaitGroup
var m sync.Mutex
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, &m)
}
w.Wait()
fmt.Println("final value of x", x)
}
final value of x 1000
当 Mutex 被一个 goroutine 调用了 Lock()
时,下一个 goroutine 再调用 Lock()
就需要等前者 Unlock()
。
你也可以用一个容量为 1 的 buffered channel 来控制这个过程,不过很不直观:
package main
import (
"fmt"
"sync"
)
var x = 0
func increment(wg *sync.WaitGroup, ch chan bool) {
ch <- true
x = x + 1
<-ch
wg.Done()
}
func main() {
var w sync.WaitGroup
ch := make(chan bool, 1)
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, ch)
}
w.Wait()
fmt.Println("final value of x", x)
}
Go 语言虽然设计上是建议 share memory by communicating 的,但是像上面这种情况,并不适合用 channel,那用 Mutex 就好。什么场景适合什么就用什么。