打印到标准输出会导致阻塞的goroutine运行吗?

huangapple go评论79阅读模式
英文:

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 &quot;fmt&quot;

func customer(id int, shop chan&lt;- int) {
	// Enter shop if seats available, otherwise leave
	// fmt.Println(&quot;Uncomment this line and the program works&quot;)
	if len(shop) &lt; cap(shop) {
		shop &lt;- id
	}
}

func barber(shop &lt;-chan int) {
	// Cut hair of anyone who enters the shop
	for {
		fmt.Println(&quot;Barber cuts hair of customer&quot;, &lt;-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&lt;- int) {
	// Enter shop if seats available, otherwise leave
	select {
	case shop &lt;- 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?

huangapple
  • 本文由 发表于 2012年4月14日 10:58:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/10150652.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定