我刚刚在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”,屏幕上没有输出,然后退出。
我的程序有问题吗?如何解决?
答案 0 :(得分:3)
这是一个相当冗长的答案,但简单地说:
time.Sleep
等到希望其他例程完成其工作是不好的。 在这个相当冗长的答案的底部,我尝试解释一些基本概念和最佳实践(以及更好的实践),您会发现代码被重写为可以正常工作并显示所有值没有依靠time.Sleep
。我尚未测试该代码,但应该没问题
对,这里有两个问题。就像一个项目符号列表:
既然您已经知道要在频道上推送多少个值,为什么不简单地创建ch := make(chan int, 100)
?这样,您的发布者就可以继续将消息推送到频道上,而不管消费者的行为如何。
您不需要 进行此操作,但是根据您要执行的操作,在频道中添加合理的缓冲区绝对值得一试。但是,目前,这两个例程都使用fmt.Println
和co,这两种方法都会成为瓶颈。打印到STDOUT是线程安全的,并已缓冲。这意味着每次对fmt.Print*
的调用都将获得一个锁,以避免两个例程中的文本合并。
您只需将所有值推送到您的频道上,然后将其关闭即可。但是,这是错误的形式。根据经验,WRT通道是在相同的例程中创建和关闭通道的。含义:您正在主例程中创建通道,应该在该通道中将其关闭。
您需要一种同步机制,或者至少保持对例程是否已完成工作的关注。这是使用sync
软件包或通过第二个渠道完成的。
// 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
通道并未在创建该通道的同一例程中关闭。但是,由于该例程被定义为闭包,因此这是可以接受的折衷方案。现在,让我们看看如何使用等待组:
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)
}
好的,这看起来很有希望,我可以让例程告诉我他们完成任务的时间。但是,如果将消费者和生产者都添加到等待组中,我将无法简单地在通道上进行迭代。仅当两个例程都调用wg.Done()
时,该通道才会关闭,但是如果使用者卡住了一个永远不会关闭的通道,那么我就创建了死锁。
此时,混合将是最简单的解决方案:将使用者添加到等待组,并使用生产者中的完成通道获取:
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")
}
答案 1 :(得分:1)
正如JimB所暗示的,time.Sleep
取time.Duration
,而不是整数。 godoc显示了如何正确调用此示例。就您而言,您可能想要:
time.Sleep(500 * time.Millisecond)
程序快速退出(但没有给您错误)的原因是由于实现time.Duration
的方式(有点令人惊讶)。
time.Duration
只是int64
的类型别名。在内部,它使用该值表示持续时间(以纳秒为单位)。当您调用time.Sleep(500)
时,编译器将很乐意将数字文字500
解释为time.Duration
。不幸的是,表示500 ns 。
time.Millisecond
是一个常量,等于毫秒(1,000,000)的纳秒数。令人高兴的是,要求您显式进行乘法运算,以使调用者可以明显看出该参数上的单位。不幸的是,time.Sleep(500)
是完全有效的go代码,但是没有大多数初学者期望的那样。
答案 2 :(得分:1)
我对您的代码进行了细微的更改(延长了时间。睡眠)。在我的Linux x86_64上工作正常
func Product(ch chan<- int) {
for i := 0; i < 10; i++ {
fmt.Println("Product:", i)
ch <- i
}
}
func Consumer(ch <-chan int) {
for i := 0; i < 10; i++ {
a := <-ch
fmt.Println("Consmuer:", a)
}
}
func main() {
ch := make(chan int, 1)
go Product(ch)
go Consumer(ch)
time.Sleep(10000)
}
输出 跑s1.go
Product: 0
Product: 1
Product: 2