• 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
    ", 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
    	}
    }
    

      

  • 相关阅读:
    MySQL的安装问题
    初识二分法
    PK赛 lower_bound( )和upper_bound( )的应用
    记录
    "双指针"去重有序数组
    归并排序(循序渐进中......)
    [2021.4.20打卡]LeetCode781. 森林中的兔子
    杂记...(持续更新)
    [未完待续](c++实现)八数码Ⅱ
    [回忆向]快速排序(降序) 感悟
  • 原文地址:https://www.cnblogs.com/sigmod3/p/9446080.html
Copyright © 2020-2023  润新知