看一段代碼:git
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。github
事實貌似是這樣。。。貌似有點小問題。。。安全
檢測競爭狀態,再把這個gosched函數註釋,而後從新檢測競爭狀態,前後編譯執行獲得的是:bash
爲何會出現這個狀況呢?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的特點通道 。atom
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\n", 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包code
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\n", name) time.Sleep(250 * time.Millisecond) if atomic.LoadInt64(&shutdown) == 1 { fmt.Printf("Shutting %s Down\n", name) break } } }
3.通道blog
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\n", name) return } n := rand.Intn(100) if n%13 == 0 { fmt.Printf("Player %s Missed\n", name) close(court) return } fmt.Printf("Player %s Hit %d\n", name, ball) ball++ court <- ball } }