Golang竞争状态

看一段代码:

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\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包

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.通道

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
	}
}

  

 

posted @ 2018-08-08 22:22  sigmod  阅读(411)  评论(0编辑  收藏  举报