以下内容大致基于“ 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
答案 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 直到互斥可用为止。