Golang競爭狀態

看一段代碼: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
	}
}
相關文章
相關標籤/搜索