go - 通过缓冲 channel 旋转进行简单锁定

标签 go

以下内容大致基于 Go 实践(第 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

最佳答案

您的代码存在数据争用(请参阅 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
$ 

关于go - 通过缓冲 channel 旋转进行简单锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51234951/

相关文章:

带缓冲区和超时的 Go writer

testing - 如何在 Golang 中测试 map 的等价性?

go - oauth token 请求错误 "missing required parameter ' client_id'",

json - 如何使用结构的类型而不是 go 中的标签重新编码结构?

Go:使用gob包将数据保存到文件以备后用安全吗?

golang 无法打印内部字符串

go - 跟踪 Go 中的 ast.Walk() 解析错误

json - 从 POST 解析 JSON

go - 寻找一种优雅的方式来解析整数

go - 在标准库中查找类型