缓冲通道旋转,简单锁定

时间:2018-07-08 18:32:52

标签: go

以下内容大致基于“ Go in Practice”(第81页):

$ cat simple_locking_with_buffered_channels.go 
package main

import(
    "fmt"
    "time"
)

func main(){
    reap := 0; sow := 0
    lock := make(chan bool,4100)
    for i:=0; i<4001; i++{
        go worker(i, lock, &reap)
        sow += 1
    }
    for reap != sow {
        fmt.Println("*yawn*")
        time.Sleep(100 * time.Millisecond)
    }
    close(lock)
}


func worker(i int, lock chan bool, reap *int){
    fmt.Printf("%d wants the lock\n", i)
    lock <-true // we acquire the lock thusly.
    fmt.Printf("%d has the lock\n", i)
    time.Sleep(500 * time.Millisecond) 
    fmt.Printf("%d is releasing the lock\n", i)
    *reap += 1
    <-lock // release
}

当我运行它时,大多数情况下它会完成,但偶尔我会看到它在打哈欠上旋转-永久如此,直到被杀死为止。是的,我可以添加超时逻辑,但是我想知道为什么会这样。

$ ps -p `pgrep  simple_locking` -o lstart,etime
                 STARTED     ELAPSED
Sun Jul  8 11:34:59 2018       02:41
$ ps -p `pgrep  simple_locking` -o lstart,etime
                 STARTED     ELAPSED
Sun Jul  8 11:34:59 2018       03:24

应该起作用,然后为什么会发生奇怪的行为。在那些情况下,为什么我的收成!=母猪?

~/golearn $ go version
go version go1.10.3 linux/amd64

我正在繁忙的老式Linux笔记本电脑上运行此程序,我感到困惑,为什么它会间歇性旋转?提前致谢!

https://play.golang.org/p/BJwAmRf1OXB

更新:1

我将代码更改为使用互斥锁(或者,我认为..)为:

package main

import(
    "fmt"
    "time"
    "sync"
)

var mutex sync.Mutex

func main(){
    reap := 0; sow := 0
    lock := make(chan bool,400)
    for i:=0; i<389; i++{
        go worker(i, lock, &reap)
        sow += 1
    }
    time.Sleep(100 * time.Millisecond)
    for reap != sow {
        fmt.Println("*yawn*")
        time.Sleep(100 * time.Millisecond)
    }
    close(lock)
}


func worker(i int, lock chan bool, reap *int){
    fmt.Printf("%d wants the lock\n", i)
    lock <-true // we acquire the lock thusly.
    fmt.Printf("%d has the lock\n", i)
    time.Sleep(500 * time.Millisecond) 
    fmt.Printf("%d is releasing the lock\n", i)
    mutex.Lock()
    *reap += 1
    mutex.Unlock()
    <-lock // release
}

这是正确的方法,因为go run --race仍然说WARNING: DATA RACE吗?

*更新3:*

尝试了go的原子计数器(需要在两次增量之间进行延迟)之后,我最终使用了互斥锁。我了解到的是:即使阅读(而不是写作)也可能使它抱怨种族状况。所以在这里,我将调用包装在使用互斥读取的函数调用中,并清除了--race测试:

$ cat improv.go
package main

import(
        "fmt"
        "time"
        "sync"
)

var mutex sync.Mutex

func main(){
        sow := 0
        reap := 0

        lock := make(chan bool,40)
        for i:=0; i<38; i++{
                go worker(i, lock, &reap)
                sow += 1
        }
        time.Sleep(100 * time.Millisecond)


        //for  get_counter(&reap) != get_counter(&sow) {
        for  get_counter(&reap) != sow {
                fmt.Println("*yawn*")
                time.Sleep(100 * time.Millisecond)
        }
}

func worker(i int, lock chan bool, reap *int){
        fmt.Printf("%d wants the lock\n", i)
        lock <-true // we acquire the lock thusly.
        fmt.Printf("%d has the lock\n", i)
        time.Sleep(500 * time.Millisecond)
        fmt.Printf("%d is releasing the lock\n", i)
        mutex.Lock()
        defer mutex.Unlock()
        *reap += 1
        <-lock // release
}

func get_counter(counter *int) int {
        mutex.Lock()
        defer mutex.Unlock()
        return *counter
}

 $ go run --race improv.go >/dev/null

3 个答案:

答案 0 :(得分:2)

您的代码具有数据竞争(请参见Go Data Race Detector)。因此,您的结果是不确定的。

$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Read at 0x00c000086010 by goroutine 7:
  main.worker()
      /home/peter/gopath/src/so/racer.go:29 +0x1c9

Previous write at 0x00c000086010 by goroutine 661:
  main.worker()
      /home/peter/gopath/src/so/racer.go:29 +0x1e2

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:13 +0xb0

Goroutine 661 (finished) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:13 +0xb0
==================
==================
WARNING: DATA RACE
Read at 0x00c000086010 by goroutine 688:
  main.worker()
      /home/peter/gopath/src/so/racer.go:29 +0x1c9

Previous write at 0x00c000086010 by goroutine 661:
  main.worker()
      /home/peter/gopath/src/so/racer.go:29 +0x1e2

Goroutine 688 (running) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:13 +0xb0

Goroutine 661 (finished) created at:
  main.main()
      /home/peter/gopath/src/so/racer.go:13 +0xb0
==================
Found 2 data race(s)
exit status 66
$ 

您的update-1代码具有数据竞争。因此,您的结果是不确定的。

$ go run -race racer.go >/dev/null
==================
WARNING: DATA RACE
Read at 0x00c000088010 by main goroutine:
  main.main()
      /home/peter/src/so/racer.go:20 +0x136

Previous write at 0x00c000088010 by goroutine 397:
  main.worker()
      /home/peter/src/so/racer.go:34 +0x1f2

Goroutine 397 (finished) created at:
  main.main()
      /home/peter/src/so/racer.go:16 +0xb0
==================
Found 1 data race(s)
exit status 66
$ 

答案 1 :(得分:2)

感谢您的指导,我了解到仅读取正在其他位置写入的变量,就可能使--race抱怨WARNING: DATA RACE。所以在这里,我将调用包装在使用互斥读取的函数调用中,并清除了--race测试。

package main

import (
    "fmt"
    "sync"
    "time"
)

var mutex sync.Mutex

func main() {
    sow := 0
    reap := 0

    lock := make(chan bool, 40)
    for i := 0; i < 38; i++ {
        go worker(i, lock, &reap)
        sow += 1
    }
    time.Sleep(100 * time.Millisecond)

    //for  get_counter(&reap) != get_counter(&sow) {
    for get_counter(&reap, &sow) {
        fmt.Println("*yawn*")
        time.Sleep(100 * time.Millisecond)
    }
}

func worker(i int, lock chan bool, reap *int) {
    fmt.Printf("%d wants the lock\n", i)
    lock <- true
    fmt.Printf("%d has the lock\n", i)
    time.Sleep(500 * time.Millisecond)
    fmt.Printf("%d is releasing the lock\n", i)
    mutex.Lock()
    defer mutex.Unlock()
    *reap += 1
    <-lock
}

func get_counter(reap *int, sow *int) bool {
    mutex.Lock()
    defer mutex.Unlock()
    return *reap == *sow
}

答案 2 :(得分:1)

您的代码中没有条件逻辑,无论是根据*reap的状态更改lock还是在继续更新之前是否等待lock的状态更改到*reap。因此,您将获得多个goroutine,它们使*reap增加而它们之间没有同步。

将您的实现与已记录的sync.Mutex行为进行比较,尤其是阻塞和等待行为:

  

锁锁定m。如果锁已在使用中,则调用goroutine   直到互斥可用为止。