转到频道不适用于生产者/消费者样本

时间:2019-01-26 16:14:31

标签: go

我刚刚在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”,屏幕上没有输出,然后退出。

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

3 个答案:

答案 0 :(得分:3)

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

  • 使用time.Sleep等到希望其他例程完成其工作是不好的。
  • 消费者和生产者除了通过渠道交换类型外,对彼此一无所知。您的代码依赖于消费者和生产者,他们知道将传递多少个整数。不现实的情况
  • 可以迭代通道(将它们视为线程安全的共享片)
  • 渠道应关闭

在这个相当冗长的答案的底部,我尝试解释一些基本概念和最佳实践(以及更好的实践),您会发现代码被重写为可以正常工作并显示所有值没有依靠time.Sleep。我尚未测试该代码,但应该没问题


对,这里有两个问题。就像一个项目符号列表:

  1. 您的频道被缓冲到1,这很好,但这不是必需的
  2. 您的频道永远不会关闭
  3. 您正在等待500ns,然后无论例程是否完成甚至退出处理都退出。
  4. 对例程没有集中控制,一旦启动它们,您将获得0控制。如果您按ctrl + c,则在编写处理重要数据的代码时可能要取消例程。检查信号处理以及与此相关的上下文

频道缓冲区

既然您已经知道要在频道上推送多少个值,为什么不简单地创建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.Sleeptime.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