笔者在前文《Golang 入门 : 理解并发与并行》和《Golang 入门 : goroutine(协程)》中介绍了 Golang 对并发的原生支持以及 goroutine 的用法。本文咱们来聊聊并发与并行带来的一些反作用。html
并行编程之因此难道较高,根本的缘由是须要处理共享资源的同步访问。好比在 Golang 中若是两个或者多个 goroutine 在没有互相同步的状况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种状况被称做竞争条件(race candition)。竞争条件的存在是让并发程序变得复杂的地方,十分容易引发潜在问题。对一个共享资源的读和写操做必须是原子化的,换句话说,同一时刻只能有一个 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 } }
运行上面的代码,输出结果以下:编程语言
Final Counter: 2
上面的程序中会对变量 counter 会进行 4 次读和写操做,每一个 goroutine 执行两次。可是,程序终止时,counter 变量的值为 2。咱们能够经过下面的图解来理解该程序的执行过程(此图来自互联网):函数
每一个 goroutine 都会覆盖另外一个 goroutine 的工做。这种覆盖发生在 goroutine 切换的时候。每一个 goroutine 创造了一个 counter 变量的副本,以后就切换到另外一个 goroutine。当 这个 goroutine 再次运行的时候,counter 变量的值已经改变了,可是 goroutine 并无更新本身的那个副本的值,而是继续使用这个副本的值,用这个值递增,并存回 counter 变量,结果覆盖了另外一个 goroutine 完成的工做。 下面是对程序执行过程的解释:spa
// 建立两个 goroutine go incCounter(1) go incCounter(2)
程序中通 go 关键字和 incCounter 函数建立了两个 goroutine。在 incCounter 函数内部对变量 counter 进行了读和写操做,而 counter 变量是这个示例程序里的共享资源。每一个 goroutine 都会先读出这个 counter 变量的值,并把 counter 变量的副本存入一个叫做 value 的本地变量。以后 incCounter 函数对 value 变量加 1,并最终将这个新值存回到 counter 变量。incCounter 函数在对本地变量 value加 1 前调用了 runtime 包的 Gosched 函数,这个调用会将 goroutine 从当前线程退出,给其余 goroutine 运行的机会。在两次操做中间这样作的目的是强制调度器切换两个 goroutine,以便让竞争条件的效果变得更明显。线程
若是不是咱们经过调用 Gosched 函数让竞争条件的效果变得明显,那么屡次运行这段程序输出的 counter 值极可能是不同的,会是 2,3,4 中的一个值。这种状况下致使的问题每每很是难以定位。code
和其它编程语言同样,Golang 提供了原子函数和锁等机制来解决同步问题。可是使用这些机制并不会使并发编程变得更简单。接下来笔者将介绍 Golang 中提供的 channel(通道)功能,看它是如何以简洁的方式解决同步问题的。协程
参考:
《Go语言实战》htm