package main import ( "fmt" "runtime" "sync" ) var ( count int32 wg sync.WaitGroup ) func main() { wg.Add(2) go incCount() go incCount() wg.Wait() fmt.Println(count) } func incCount() { defer wg.Done() for i := 0; i < 2; i++ { value := count runtime.Gosched() value++ count = value } }这是一个资源竞争的例子,大家可以将程序多运行几次,会发现结果可能是 2,也可以是 3,还可能是 4。这是因为 count 变量没有任何同步保护,所以两个 goroutine 都会对其进行读写,会导致对已经计算好的结果被覆盖,以至于产生错误结果。
go build -race
命令。在项目目录下执行这个命令,生成一个可以执行文件,然后再运行这个可执行文件,就可以看到打印出的检测信息。go build
命令中多加了一个-race
标志,这样生成的可执行程序就自带了检测资源竞争的功能,运行生成的可执行文件,效果如下所示:
==================
WARNING: DATA RACE
Read at 0x000000619cbc by goroutine 8:
main.incCount()
D:/code/src/main.go:25 +0x80
Previous write at 0x000000619cbc by goroutine 7:
main.incCount()
D:/code/src/main.go:28 +0x9f
Goroutine 8 (running) created at:
main.main()
D:/code/src/main.go:17 +0x7e
Goroutine 7 (finished) created at:
main.main()
D:/code/src/main.go:16 +0x66
==================
4
Found 1 data race(s)
value := count
,而这时 goroutine 7 在代码 28 行修改共享资源 count = value
,而这两个 goroutine 都是从 main 函数的 16、17 行通过 go 关键字启动的。package main import ( "fmt" "runtime" "sync" "sync/atomic" ) var ( counter int64 wg sync.WaitGroup ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() //等待goroutine结束 fmt.Println(counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { atomic.AddInt64(&counter, 1) //安全的对counter加1 runtime.Gosched() } }上述代码中使用了 atmoic 包的 AddInt64 函数,这个函数会同步整型值的加法,方法是强制同一时刻只能有一个 gorountie 运行并完成这个加法操作。当 goroutine 试图去调用任何原子函数时,这些 goroutine 都会自动根据所引用的变量做同步处理。
package main import ( "fmt" "sync" "sync/atomic" "time" ) var ( shutdown int64 wg sync.WaitGroup ) func main() { wg.Add(2) go doWork("A") go doWork("B") time.Sleep(1 * time.Second) fmt.Println("Shutdown Now") atomic.StoreInt64(&shutdown, 1) wg.Wait() } func doWork(name string) { defer wg.Done() for { fmt.Printf("Doing %s Work\n", name) time.Sleep(250 * time.Millisecond) if atomic.LoadInt64(&shutdown) == 1 { fmt.Printf("Shutting %s Down\n", name) break } } }上面代码中 main 函数使用 StoreInt64 函数来安全地修改 shutdown 变量的值。如果哪个 doWork goroutine 试图在 main 函数调用 StoreInt64 的同时调用 LoadInt64 函数,那么原子函数会将这些调用互相同步,保证这些操作都是安全的,不会进入竞争状态。
package main import ( "fmt" "runtime" "sync" ) var ( counter int64 wg sync.WaitGroup mutex sync.Mutex ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() fmt.Println(counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { //同一时刻只允许一个goroutine进入这个临界区 mutex.Lock() { value := counter runtime.Gosched() value++ counter = value } mutex.Unlock() //释放锁,允许其他正在等待的goroutine进入临界区 } }同一时刻只有一个 goroutine 可以进入临界区。之后直到调用 Unlock 函数之后,其他 goroutine 才能进去临界区。当调用 runtime.Gosched 函数强制将当前 goroutine 退出当前线程后,调度器会再次分配这个 goroutine 继续运行。
本文链接:http://task.lmcjl.com/news/14344.html