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 < 200000; i++ {
value := count
runtime.Gosched()
value++
count = value
}
}
Go为我们提供了一个工具帮助我们检查是否存在共享资源竞争的问题,本范例Goroutine 6 这个协程发现了一个资源竞争问题,证明上述代码写的有问题啦
[root@contoso100 ~]# cd $GOPATH/src/contoso.org/hello && go build -race [root@contoso100 hello]# hello 200162 [root@contoso100 hello]# ./hello ================== WARNING: DATA RACE Read at 0x0000005880e0 by goroutine 6: main.incCount() /root/code/go/src/contoso.org/hello/main.go:24 +0x78 Previous write at 0x0000005880e0 by goroutine 7: main.incCount() /root/code/go/src/contoso.org/hello/main.go:27 +0x9a Goroutine 6 (running) created at: main.main() /root/code/go/src/contoso.org/hello/main.go:16 +0x5f Goroutine 7 (running) created at: main.main() /root/code/go/src/contoso.org/hello/main.go:17 +0x77 ================== 384626 Found 1 data race(s) [root@contoso100 hello]#
输出结果表明,找到了一个资源竞争,连在那一行代码出了问题,都标示出来了。Goroutine 6 在代码第24行读取共享资源 value := count,
而这时Goroutine 7 正在代码第27行修改共享资源 count = value,同时有2个Goroutine对其进行了读写操作,所以就出现了共享资源竞争导致输出结果不确定了,而
Goroutine 6 (running) created at: main.main()
/root/code/go/src/contoso.org/hello/main.go:16 +0x5f
Goroutine 7 (running) created at: main.main()
/root/code/go/src/contoso.org/hello/main.go:17 +0x77
这两个Goroutine都是从main函数启动的,在代码的第16行、第17行,是通过go关键字启动的协程
[root@contoso100 hello]# tree ## bin目录也有一个hello可执行程序,与下面这个hello不是同一个可执行程序,所以上面执行hello 会出现不同的输出现象 . ├── debug ├── hello └── main.go 0 directories,3 files [root@contoso100 hello]#
再看另外一种情形:
package main import ( "fmt" "runtime" "sync" "sync/atomic" ) 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 < 200000; i++ { value := atomic.LoadInt32(&count) runtime.Gosched() value++ atomic.StoreInt32(&count,value) } }
[root@contoso100 ~]# cd $GOPATH/src/contoso.org/hello && go build -race [root@contoso100 hello]# ./hello 211047 [root@contoso100 hello]# ./hello 212399 [root@contoso100 hello]# ./hello 217222 [root@contoso100 hello]# ./hello 237363 [root@contoso100 hello]# 没能检测出有共享资源竞争,但是输出的结果还是一个不确定的数,正确的结果应该输出400000,象下面
func incCount() { defer wg.Done() for i := 0; i < 200000; i++ { runtime.Gosched() atomic.AddInt32(&count,int32(1)) } }
这样调整代码就没问题了,也可这样调整代码同样没有问题
func incCount() { defer wg.Done() for i := 0; i < 200000; i++ { atomic.AddInt32(&count,int32(1)) runtime.Gosched() } }
源码如下调整也没有问题 使用比较交换的方式 (cas) :
package main import ( "fmt" "runtime" "sync" "sync/atomic" ) 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 < 200000; i++ { addValue(int32(1)) runtime.Gosched() } } //不断地尝试原子地更新count的值,直到操作成功为止 func addValue(delta int32) { //在被操作值被频繁变更的情况下,CAS操作并不那么容易成功 //so 不得不利用for循环以进行多次尝试 for { //old := count //在进行读取value的操作的过程中,其他对此值的读写操作是可以被同时进行的,那么这个读操作很可能会读取到一个只被修改了一半的数据. //因此我们要使用载入 old := atomic.LoadInt32(&count) //返回该指针指向的那个值 //先判断参数&count指向的被操作值与参数old的值是否相等, //若上述判断是相等的,那么就执行 old = old + delta 新值替代旧值的操作;否则就忽略这种替换操作 if atomic.CompareAndSwapInt32(&count,old,old+delta) { //在函数CompareAndSwapInt32新值替代旧值成功并返回true时,退出死循环 break } //操作失败的缘由总会是count的旧值已不与old的值相等了. //CAS操作虽然不会让某个Goroutine阻塞在某条语句上,但是仍可能会使流产的执行暂时停一下,不过时间大都极其短暂. } }
使用互斥锁 sync.Mutex 实现也没有问题: package main import ( "fmt" "runtime" "sync" ) var ( count int32 wg sync.WaitGroup mutex sync.Mutex ) func main() { wg.Add(2) go incCount() go incCount() wg.Wait() fmt.Println(count) } func incCount() { defer wg.Done() for i := 0; i < 200000; i++ { mutex.Lock() value := count runtime.Gosched() value++ count = value mutex.Unlock() } } (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|