gpt4 book ai didi

Go channel 不适用于生产者/消费者样本

转载 作者:IT王子 更新时间:2023-10-29 01:51:49 25 4
gpt4 key购买 nike

我刚刚在 Mac 上安装了 Go,这是代码

package main

import (
"fmt"
"time"
)

func Product(ch chan<- int) {
for i := 0; i < 100; i++ {
fmt.Println("Product:", i)
ch <- i
}
}

func Consumer(ch <-chan int) {
for i := 0; i < 100; i++ {
a := <-ch
fmt.Println("Consmuer:", a)
}
}

func main() {
ch := make(chan int, 1)
go Product(ch)
go Consumer(ch)
time.Sleep(500)
}

我“去运行 producer_consumer.go”,屏幕上没有输出,然后退出。

我的程序有问题吗?如何解决?

最佳答案

这是一个相当冗长的答案,但简单地说:

  • 使用time.Sleep 等待直到希望其他例程完成他们的工作是不好的。
  • 除了他们通过 channel 交换的类型外,消费者和生产者不应该知道彼此的任何信息。您的代码依赖于消费者和生产者都知道将传递多少整数。不现实的场景
  • channel 可以迭代(将它们视为线程安全的共享 slice )
  • channel 应该关闭

在这个相当冗长的答案的底部,我试图解释一些基本概念和最佳实践(好吧,更好的实践),您会发现您的代码被重写以显示所有值依赖time.Sleep。我没有测试该代码,但应该没问题


是的,这里有几个问题。正如项目符号列表:

  1. 你的 channel 缓冲为 1,这很好,但没有必要
  2. 您的 channel 永远不会关闭
  3. 您等待 500 纳秒,然后退出,无论例程是否已完成,甚至是否已开始处理该问题。
  4. 没有对例程的集中控制,一旦你启动它们,你就没有控制权。如果您按下 ctrl+c,您可能希望在编写处理重要数据的代码时取消例程。检查信号处理和上下文

channel 缓冲区

鉴于您已经知道要将多少值推送到 channel ,为什么不简单地创建 ch := make(chan int, 100)?这样,您的发布者就可以继续将消息推送到 channel ,而不管消费者做什么。

不需要这样做,但是根据您要执行的操作向您的 channel 添加一个合理的缓冲区绝对值得一试。不过目前,这两个例程都在使用 fmt.Println & co,无论哪种方式都将成为瓶颈。打印到 STDOUT 是线程安全的,并且是缓冲的。这意味着每次调用 fmt.Print* 都会获取一个锁,以避免合并来自两个例程的文本。

关闭 channel

您可以简单地将所有值推送到您的 channel ,然后关闭它。然而,这是一种糟糕的形式。 WRT channel 的经验法则是 channel 是在同一个例程中创建和关闭的。意思是:您正在主例程中创建 channel ,这是它应该关闭的地方。

您需要一种同步机制,或者至少密切关注您的例程是否已完成其工作。这是使用 sync 包或通过第二个 channel 完成的。

// using a done channel
func produce(ch chan<- int) <-chan struct{} {
done := make(chan struct{})
go func() {
for i := 0; i < 100; i++ {
ch <- i
}
// all values have been published
// close done channel
close(done)
}()
return done
}

func main() {
ch := make(chan int, 1)
done := produce(ch)
go consume(ch)
<-done // if producer has done its thing
close(ch) // we can close the channel
}

func consume(ch <-chan int) {
// we can now simply loop over the channel until it's closed
for i := range ch {
fmt.Printf("Consumed %d\n", i)
}
}

好的,但是在这里您仍然需要等待 consume 例程完成。

您可能已经注意到 done channel 在技术上并没有在创建它的同一个例程中关闭。但是,因为例程被定义为闭包,所以这是一个可以接受的折衷方案。现在让我们看看如何使用 WaitGroup :

import (
"fmt"
"sync"
)

func product(wg *sync.WaitGroup, ch chan<- int) {
defer wg.Done() // signal we've done our job
for i := 0; i < 100; i++ {
ch <- i
}
}

func main() {
ch := make(chan int, 1)
wg := sync.WaitGroup{}
wg.Add(1) // I'm adding a routine to the channel
go produce(&wg, ch)
wg.Wait() // will return once `produce` has finished
close(ch)
}

好的,所以这看起来很有希望,我可以让例程在完成任务时告诉我。但是,如果我将消费者和生产者都添加到 WaitGroup ,我就不能简单地遍历 channel 。只有当两个例程都调用 wg.Done() 时, channel 才会关闭,但如果消费者卡在一个永远不会关闭的 channel 上循环,那么我就创建了一个死锁。

解决方案:

此时混合将是最简单的解决方案:将消费者添加到 WaitGroup ,并使用生产者中的完成 channel 来获取:

func produce(ch chan<- int) <-chan struct{} {
done := make(chan struct{})
go func() {
for i := 0; i < 100; i++ {
ch <- i
}
close(done)
}()
return done
}

func consume(wg *sync.WaitGroup, ch <-chan int) {
defer wg.Done()
for i := range ch {
fmt.Printf("Consumer: %d\n", i)
}
}

func main() {
ch := make(chan int, 1)
wg := sync.WaitGroup{}
done := produce(ch)
wg.Add(1)
go consume(&wg, ch)
<- done // produce done
close(ch)
wg.Wait()
// consumer done
fmt.Println("All done, exit")
}

关于Go channel 不适用于生产者/消费者样本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54380193/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com