Golang: Language: Concurrency: Race Condition

26th December 2020 at 11:13pm
Golang: Language: Concurrency

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