使用通道退出永久循环-Go游乐场问题

使用通道退出永久循环-Go游乐场问题,go,go-playground,Go,Go Playground,我试图实现一个简单的逻辑,其中生产者使用永久for循环将数据发送到通道ch,消费者从通道ch读取数据 当生产者在通道quit上接收到信号时,生产者停止生产并退出永久循环 代码如下(另请参见) func main(){ ch:=制造(成交量) 退出:=make(chan bool) var wg sync.WaitGroup 工作组.添加(1) 开始生产(ch、退出和工作组) 去消费(ch) 时间。睡眠(1*时间。毫秒) fmt.Println(“关闭”) 关闭(退出) wg.Wait() } f

我试图实现一个简单的逻辑,其中生产者使用永久
for
循环将数据发送到通道
ch
,消费者从通道
ch
读取数据

当生产者在通道
quit
上接收到信号时,生产者停止生产并退出永久循环

代码如下(另请参见)

func main(){
ch:=制造(成交量)
退出:=make(chan bool)
var wg sync.WaitGroup
工作组.添加(1)
开始生产(ch、退出和工作组)
去消费(ch)
时间。睡眠(1*时间。毫秒)
fmt.Println(“关闭”)
关闭(退出)
wg.Wait()
}
func product(ch chan int,quit chan bool,wg*sync.WaitGroup){
对于i:=0;;i++{
挑选{

case您创建了一个不应该在真实程序中发生的病态情况,因此调度程序没有优化以处理此问题。再加上操场上的假时间实现,在达到超时之前,您会得到太多的生产者和消费者周期

制作人goroutine正在尽可能快地创建值,而消费者总是准备好接收这些值。使用
GOMAPXPROCS=1
,调度程序将所有时间都花在这两者之间,然后被迫抢占可用的工作来检查主goroutine,这比游乐场允许的时间要长

如果我们为生产者-消费者对添加一些要做的事情,我们可以限制他们垄断调度程序的时间。例如,向消费者添加
time.Sleep(time.Microsecond)
,将导致游乐场打印1000个值。这也表明了“精确性”模拟时间在操场上,因为正常的硬件不可能做到这一点,因为处理每条消息需要非零的时间

虽然这是一个有趣的案例,但它与实际的程序几乎没有关系

请注意,您可以通过通道
范围
来接收所有值,您应该始终
延迟wg.Done
在goroutine开始时,如果可能,您可以在
选择
案例中发送值,该案例允许您在发送未就绪时实际取消for-select循环,并且如果您希望“退出消费者”消息您还需要将
WaitGroup
发送给消费者

func main(){
ch:=制造(成交量)
退出:=make(chan bool)
var wg sync.WaitGroup
工作组.添加(2)
开始生产(ch、退出和工作组)
去消费(ch和wg)
时间。睡眠(50*时间。微秒)
fmt.Println(“关闭”)
关闭(退出)
wg.Wait()
}
func product(ch chan int,quit chan bool,wg*sync.WaitGroup){
推迟工作组完成()
对于i:=0;;i++{
挑选{

如果您的goroutine基本上不起作用,那么就创建了一个“繁忙循环”"在生产者和消费者之间。如果运行时抢占这一点需要一定的时间,而且在操场上时间太长。操场上的假时间实现也可能存在问题,在繁忙循环中的goroutines理论上可以打印出比停止前预期多得多的值,从而导致大量的pr处理处理输出缓冲区所花费的时间。仔细观察,虽然过于繁忙的生产者和消费者很容易解释,但我认为操场
时间
是这里更有趣的事情。就像在科学中我们使用理想气体或球形奶牛一样,操场上的goroutines似乎以“无限速度”运行在可观察的范围之外。但即使在您发布的链接中,消费者打印也会出现在生产者打印之前。这是故意的吗?@Inian:print语句发生在发送和接收完成之后,否则不会同步。您看到每个值的顺序并不重要。
func main() {
    ch := make(chan int)
    quit := make(chan bool)
    var wg sync.WaitGroup
    wg.Add(1)
    go produce(ch, quit, &wg)
    go consume(ch)
    time.Sleep(1 * time.Millisecond)
    fmt.Println("CLOSE")
    close(quit)
    wg.Wait()
}

func produce(ch chan int, quit chan bool, wg *sync.WaitGroup) {
    for i := 0; ; i++ {
        select {
        case <-quit:
            close(ch)
            fmt.Println("exit")
            wg.Done()
            return //we exit
        default:
            ch <- i
            fmt.Println("Producer sends", i)
        }
    }
}

func consume(ch chan int) {
    for {
        runtime.Gosched() // give the opportunity to the main goroutine to close the "quit" channel
        select {
        case i, more := <-ch:
            if !more {
                fmt.Println("exit consumer")
                return
            }
            fmt.Println("Consumer receives", i)
        }
    }
}
func main() {
    ch := make(chan int)
    quit := make(chan bool)
    var wg sync.WaitGroup
    wg.Add(2)
    go produce(ch, quit, &wg)
    go consume(ch, &wg)
    time.Sleep(50 * time.Microsecond)
    fmt.Println("CLOSE")
    close(quit)
    wg.Wait()
}

func produce(ch chan int, quit chan bool, wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; ; i++ {
        select {
        case <-quit:
            close(ch)
            fmt.Println("exit")
            return
        case ch <- i:
            fmt.Println("Producer sends", i)
        }
    }
}

func consume(ch chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    
    for i := range ch {
        fmt.Println("Consumer receives", i)
        time.Sleep(time.Microsecond)
    }
    
    fmt.Println("exit consumer")
    return
}