看一段代码:
package main import ( "fmt" "runtime" "sync" ) var ( counter int wg sync.WaitGroup ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() fmt.Println("Final Counter:", counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { value := id runtime.Gosched() value++ counter = value } }
goroutine执行的是副本值,然后将副本值写入counter,所以在切换goroutine时,goroutine中的值会覆盖counter。其中Gosched函数是runtime包中用于将goroutine从当前线程退出,给其它goroutine运行的机会。这段代码执行下来理论上应该是存在竞争状态的,对于counter这个变量,在两个goroutine的切换下,一共加了4次,但是由于每次切换后进入队列的并不是真的这个值,而是一个副本,结果输出应该为2。
事实貌似是这样。。。貌似有点小问题。。。
检测竞争状态,再把这个gosched函数注释,然后重新检测竞争状态,先后编译执行得到的是:
为什么会出现这个情况呢?Final Counter: 3
================== WARNING: DATA RACE Write at 0x0000005b73c0 by goroutine 6: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Previous write at 0x0000005b73c0 by goroutine 7: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Goroutine 6 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68 Goroutine 7 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89 ================== Final Counter: 2 Found 1 data race(s)
================== WARNING: DATA RACE Write at 0x0000005b73c0 by goroutine 7: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Previous write at 0x0000005b73c0 by goroutine 6: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Goroutine 7 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89 Goroutine 6 (finished) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68 ================== Final Counter: 3 Found 1 data race(s)================
输出小概率有3的情况,可能是goroutine没有退出,所以发生了新goroutine中的值与上一次goroutine副本值相加的情况。对于这样存在多个goroutine对一个共享资源进行操作的功能还是需要对其加锁,或使用简单的atomic,以及使用Go的特色通道 。
1.互斥锁
package main import ( "fmt" "runtime" "sync" ) var ( counter int wg sync.WaitGroup //互斥锁 mutex sync.Mutex ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() fmt.Printf("Final Counter: %d ", counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { //互斥锁锁定的临界代码块,只允许同一时刻只有一个goroutine访问 mutex.Lock() { //习惯地加上大括号更清晰 // Capture the value of counter. value := counter // Yield the thread and be placed back in queue. runtime.Gosched() // Increment our local value of counter. value++ // Store the value back into counter. counter = value } mutex.Unlock() //解锁 } }
2.atomic包
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") //设定goroutine执行的时间 time.Sleep(1 * time.Second) fmt.Println("Shutdown Now") //安全标志 判断是否可以停止goroutine工作 atomic.StoreInt64(&shutdown, 1) wg.Wait() } func doWork(name string) { defer wg.Done() for { fmt.Printf("Doing %s Work ", name) time.Sleep(250 * time.Millisecond) if atomic.LoadInt64(&shutdown) == 1 { fmt.Printf("Shutting %s Down ", name) break } } }
3.通道
package main import ( "fmt" "math/rand" "sync" "time" ) var wg sync.WaitGroup func init() { rand.Seed(time.Now().UnixNano()) } func main() { court := make(chan int) wg.Add(2) go player("Ding", court) go player("Sha", court) court <- 1 wg.Wait() } func player(name string, court chan int) { defer wg.Done() for { ball, ok := <-court if !ok { fmt.Printf("Player %s Won ", name) return } n := rand.Intn(100) if n%13 == 0 { fmt.Printf("Player %s Missed ", name) close(court) return } fmt.Printf("Player %s Hit %d ", name, ball) ball++ court <- ball } }