有mammalian,就有天然资源市场竞争,假如三个或是数个goroutine在没互相并行的情况下,出访某一共享资源的天然资源,比如说与此同时对该天然资源展开随机存取时,就会处在互相市场竞争的状况,这是mammalian中的天然资源市场竞争。
mammalian这类并不繁杂,但即使有了天然资源市场竞争的难题,就使他们合作开发Dhanbad的mammalian程序变得复杂出来,即使会引发许多莫名的难题。
packagemain
import(
“fmt” “runtime”“sync”
)
var ( count int32wg sync.WaitGroup )
func main() { wg.Add(2)
goincCount()
goincCount() wg.Wait() fmt.Println(count) }
funcincCount() {
deferwg.Done()
for i := 0; i < 2; i++ { value := count runtime.Gosched() value++ count = value } }这是一个天然资源市场竞争的例子,他们可以多运行几次这个程序,会发现结果可能是2,也可以是3,也可能是4。即使共享资源天然资源count变量没任何并行保护,所以三个goroutine都会对其展开随机存取,会导致对已经计算好的结果覆盖,以至于产生错误结果,这里他们演示一种可能,三个goroutine他们暂时称之为g1和g2。
g1读取到count为0。
然后g1暂停了,切换到g2运行,g2读取到count也为0。
g2暂停,切换到g1,g1对count+1,count变为1。
有没注意到,刚刚g1对count+1的结果被g2给覆盖了,三个goroutine都+1还是1
不再继续演示下去了,到这里结果已经错了,三个goroutine互相覆盖结果。他们这里的runtime.Gosched()是让当前goroutine暂停的意思,退回执行队列,让其他等待的goroutine运行,目的是让他们演示天然资源市场竞争的结果更明显。注意,这里还会牵涉到CPU难题,多核会并行,那么天然资源市场竞争的效果更明显。
所以他们对于同一个天然资源的随机存取必须是原子化的,也是说,同一时间只能有一个goroutine对共享资源天然资源展开随机存取操作。
共享资源天然资源市场竞争的难题,非常繁杂,并且难以察觉,好在Go为他们提供了一个工具帮助他们检查,这个是go build -race命令。他们在当前项目目录下执行这个命令,生成一个可以执行文件,然后再运行这个可执行文件,就可以看到打印出的检测信息。
go build -race多加了一个-race标志,这样生成的可执行程序就自带了检测天然资源市场竞争的功能,下面他们运行,也是在终端运行。
./hello我这里示例生成的可执行文件名是hello,所以是这么运行的,这时候,他们看终端输出的检测结果。
➜ hello ./hello ================== WARNING: DATA RACE Read at0x0000011a5118 by goroutine 7: main.incCount() /Users/xxx/code/go/src/flysnow.org/hello/main.go:25 +0x76 Previous write at 0x0000011a5118 by goroutine6: main.incCount() /Users/xxx/code/go/src/flysnow.org/hello/main.go:28 +0x9a Goroutine7(running) created at: main.main() /Users/xxx/code/go/src/flysnow.org/hello/main.go:17 +0x77 Goroutine 6(finished) created at: main.main() /Users/xxx/code/go/src/flysnow.org/hello/main.go:16 +0x5f ==================
4
Found 1 data race(s)看,找到一个天然资源市场竞争,连在那一行代码出了问题,都标示出来了。goroutine 7在代码25行读取共享资源天然资源value := count,而这时goroutine 6正在代码28行修改共享资源天然资源count = value,而这三个goroutine都是从main函数启动的,在16、17行,通过go关键字。
既然他们已经知道共享资源天然资源市场竞争的难题,是即使与此同时有三个或是数个goroutine对其展开了随机存取,那么他们只要保证,与此同时只有一个goroutine随机存取不就可以了,现在他们就看下传统解决天然资源市场竞争的办法—对天然资源加锁。
Go词汇提供了atomic包和sync包里的一些函数对共享资源天然资源并行枷锁,他们先看下atomic包。
packagemain
import(
“fmt” “runtime” “sync”“sync/atomic”
)
var ( count int32wg sync.WaitGroup )
funcmain() { wg.Add(2)
goincCount()
goincCount() wg.Wait() fmt.Println(count) }
funcincCount() {
deferwg.Done()
for i := 0; i < 2; i++ { value := atomic.LoadInt32(&count) runtime.Gosched() value++ atomic.StoreInt32(&count,value) } }留意这里atomic.LoadInt32和atomic.StoreInt32三个函数,一个读取int32类型变量的值,一个是修改int32类型变量的值,这三个都是原子性的操作,Go已经帮助他们在底层使用加锁机制,保证了共享资源天然资源的并行和安全,所以他们可以得到正确的结果,这时候他们再使用天然资源市场竞争检测工具go build -race检查,也不会提示有难题了。
atomic包里还有许多原子化的函数可以保证mammalian下天然资源并行出访修改的难题,比如说函数atomic.AddInt32可以直接对一个int32类型的变量展开修改,在原值的基础上再增加多少的功能,也是原子性的,这里不再举例,大家自己可以试试。
atomic虽然可以解决天然资源市场竞争难题,但比较都是比较简单的,支持的数据类型也有限,所以Go词汇还提供了一个sync包,这个sync包里提供了一种互斥型的锁,可以让他们自己灵活的控制哪些代码,与此同时只能有一个goroutine出访,被sync互斥锁控制的这段代码范围,被称之为临界区,临界区的代码,同一时间,只能又一个goroutine出访。刚刚那个例子,他们还可以这么改造。
packagemain
import(
“fmt” “runtime”“sync”
)
var ( count int32wg sync.WaitGroup mutex sync.Mutex )
func main() { wg.Add(2)
goincCount()
goincCount() wg.Wait() fmt.Println(count) }
funcincCount() {
deferwg.Done()
for i := 0; i < 2; i++ { mutex.Lock() value := count runtime.Gosched() value++ count = value mutex.Unlock() } }实例中,新声明了一个互斥锁mutex sync.Mutex,这个互斥锁有三个方法,一个是mutex.Lock(),一个是mutex.Unlock(),这三个之间的区域是临界区,临界区的代码是安全的。
示例中他们先调用mutex.Lock()对有市场竞争天然资源的代码加锁,这样当一个goroutine进入这个区域的时候,其他goroutine就进不来了,只能等待,一直到调用mutex.Unlock() 释放这个锁为止。
这种方式比较灵活,可以让代码编写者任意定义需要保护的代码范围,也是临界区。除了原子函数和互斥锁,Go还为他们提供了更容易在数个goroutine并行的功能,这是通道chan,他们下篇讲。
感谢支持。