Golang: Language: Concurrency: Race Condition

 26th December 2020 at 11:13pm

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 就好。什么场景适合什么就用什么。