英文:
Printing to stdout causes blocked goroutine to run?
问题
作为一个愚蠢的基本线程练习,我一直在尝试在golang中实现睡觉的理发师问题。使用通道应该很容易,但我遇到了一个Heisenbug。也就是说,当我尝试诊断它时,问题就消失了!
考虑以下内容。main()
函数将整数(或“顾客”)推送到shop
通道。barber()
函数从shop
通道读取以剪“顾客”的头发。如果我在customer()
函数中插入一个fmt.Print
语句,程序将按预期运行。否则,barber()
函数将不会剪任何人的头发。
package main
import "fmt"
func customer(id int, shop chan<- int) {
// 如果有座位可用,则进入商店,否则离开
// fmt.Println("取消注释此行,程序将正常运行")
if len(shop) < cap(shop) {
shop <- id
}
}
func barber(shop <-chan int) {
// 为进入商店的任何人理发
for {
fmt.Println("理发师为顾客理发", <-shop)
}
}
func main() {
shop := make(chan int, 5) // 有五个座位可用
go barber(shop)
for i := 0; ; i++ {
customer(i, shop)
}
}
有什么想法是怎么回事?
英文:
<!-- language-all: lang-go -->
As a silly basic threading exercise, I've been trying to implement the sleeping barber problem in golang. With channels this should be quite easy, but I've run into a heisenbug. That is, when I try to diagnose it, the problem disappears!
Consider the following. The main()
function pushes integers (or "customers") onto the shop
channel. barber()
reads the shop
channel to cut "customers'" hair. If I insert a fmt.Print
statement into the customer()
function, the program runs as expected. Otherwise, barber()
never cuts anyone's hair.
package main
import "fmt"
func customer(id int, shop chan<- int) {
// Enter shop if seats available, otherwise leave
// fmt.Println("Uncomment this line and the program works")
if len(shop) < cap(shop) {
shop <- id
}
}
func barber(shop <-chan int) {
// Cut hair of anyone who enters the shop
for {
fmt.Println("Barber cuts hair of customer", <-shop)
}
}
func main() {
shop := make(chan int, 5) // five seats available
go barber(shop)
for i := 0; ; i++ {
customer(i, shop)
}
}
Any idea what's afoot?
答案1
得分: 6
问题在于Go的调度器的实现方式。当前的goroutine只有在进行系统调用或阻塞通道操作时才能让出给其他的goroutine。fmt.Println
进行了系统调用,给了goroutine让出的机会。否则它就没有机会。
实际上,这通常并不重要,但对于像这样的小问题,有时可能会出现这种情况。
此外,一种更符合惯用法、更少竞争的在通道上进行非阻塞发送的方式是:
func customer(id int, shop chan<- int) {
// 如果有座位,则进入商店,否则离开
select {
case shop <- id:
default:
}
}
你现在的做法是,由于在实际进行发送时,len(shop)
可能已经发生了变化,一个顾客可能最终会等在理发店外面。
英文:
The problem is the way Go's scheduler is implemented. The current goroutine can yield to other goroutines only when it makes a system call or a blocking channel operation. fmt.Println
makes a system call, giving the goroutine an opportunity to yield. Otherwise it doesn't have one.
In practice this doesn't often matter, but for small problems like this it can sometimes crop up.
Also, a more idiomatic, less racy way of doing a non-blocking send on a channel is:
func customer(id int, shop chan<- int) {
// Enter shop if seats available, otherwise leave
select {
case shop <- id:
default:
}
}
The way you're doing it, a customer could end up waiting outside of the barber shop since by the time you actually do the send, len(shop)
may have changed.
答案2
得分: 1
在main函数的开头添加runtime.GOMAXPROCS(2)
可以解决这个问题吗?
英文:
Does adding runtime.GOMAXPROCS(2)
at the beginning of main solves this?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论