Golang 入门 : 竞争条件
笔者在前文《Golang 入门 : 理解并发与并行》和《Golang 入门 : goroutine(协程)》中介绍了 Golang 对并发的原生支持以及 goroutine 的用法。本文我们来聊聊并发与并行带来的一些副作用。
并行编程之所以难道较高,根本的原因是需要处理共享资源的同步访问。比如在 Golang 中如果两个或者多个 goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争条件(race candition)。竞争条件的存在是让并发程序变得复杂的地方,十分容易引起潜在问题。对一个共享资源的读和写操作必须是原子化的,换句话说,同一时刻只能有一个 goroutine 对共享资源进行读和写操作。
goroutine 引入的竞争条件
让我们来通过下面的 demo 来观察 goroutine 引入的竞争条件,为了让观察结果明显,我们采取了一些极端措施:
package main
import (
"sync"
"fmt"
"runtime"
)
var(
// counter是所有goroutine都要增加其值的变数
counter int
// wg用来等待程序结束
wg sync.WaitGroup
)
// main是所有Go程序的入口
func main(){
runtime.GOMAXPROCS(1)
// 计数加2,表示要等待两个goroutine
wg.Add(2)
// 创建两个goroutine
go incCounter(1)
go incCounter(2)
// 等待goroutine结束
wg.Wait()
fmt.Println("Final Counter:", counter)
}
// incCounter增加包里counter变数的值
func incCounter(id int){
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done()
for count := 0; count < 2; count++{
// 捕获counter的值
value := counter
// 当前goroutine从线程退出,并放回到队列
runtime.Gosched()
// 增加本地value变数的值
value++
// 将该值保存回counter
counter = value
}
}
上面的程序中会对变数 counter 会进行 4 次读和写操作,每个 goroutine 执行两次。但是,程序终止时,counter 变数的值为 2。我们可以通过下面的图解来理解该程序的执行过程(此图来自互联网):