【问题标题】:How to safely interact with channels in goroutines in Golang如何在 Golang 中安全地与 goroutine 中的通道交互
【发布时间】:2018-10-02 22:01:57
【问题描述】:

我是新手,我正在尝试了解 goroutine 中通道的工作方式。据我了解,关键字range 可用于迭代通道的值,直到通道关闭或缓冲区用完为止;因此,for range c 将重复循环,直到缓冲区用完。

我有以下简单的函数可以为频道增加价值:

func main() {

    c := make(chan int)
    go printchannel(c)
    for i:=0; i<10 ; i++ {
        c <- i
    }

}

我有两个printchannel 的实现,但我不确定为什么行为不同。

实施 1:

func printchannel(c chan int) {
    for range c {
        fmt.Println(<-c)
    }
}

输出:1 3 5 7

实施 2:

func printchannel(c chan int) {
    for i:=range c {
        fmt.Println(i)
    }
}

输出:0 1 2 3 4 5 6 7 8

我没想到这些输出!

想要的输出:0 1 2 3 4 5 6 7 8 9

main 函数和printchannel 函数是否应该在两个线程上并行运行,一个向通道添加值,另一个读取值直到通道关闭?我可能在这里遗漏了一些基本的 go/thread 概念,并且指向它的指针会有所帮助。

非常感谢对此的反馈(以及我对 goroutine 中通道操作的理解)!

【问题讨论】:

  • range 范围直到通道关闭。 “缓冲区用完”与范围的工作方式无关(无论缓冲区用完意味着什么)。
  • 我还找到了另一个解决方案,即使用defer。最终我可以在打印内容之前go func() { defer close(ch) }

标签: multithreading go range goroutine channels


【解决方案1】:

实现 1. 您从通道读取两次 - range c&lt;-c 都从通道读取。

实施 2。这是正确的方法。您可能看不到 9 打印的原因是两个 goroutine 可能在并行线程中运行。在这种情况下,它可能会这样:

  1. main goroutine 将 9 发送到通道并阻塞直到它被读取
  2. 第二个 goroutine 从通道接收 9
  3. main goroutine 解除阻塞并退出。这会终止整个程序,不会给第二个 goroutine 打印 9 的机会

在这种情况下,您必须同步您的 goroutine。比如像这样

func printchannel(c chan int, wg *sync.WaitGroup) {
    for i:=range c {
        fmt.Println(i)
    }

    wg.Done() //notify that we're done here
}

func main() {
    c := make(chan int)
    wg := sync.WaitGroup{}

    wg.Add(1) //increase by one to wait for one goroutine to finish
              //very important to do it here and not in the goroutine
              //otherwise you get race condition

    go printchannel(c, &wg) //very important to pass wg by reference
                            //sync.WaitGroup is a structure, passing it
                            //by value would produce incorrect results

    for i:=0; i<10 ; i++ {
        c <- i
    }

    close(c)  //close the channel to terminate the range loop
    wg.Wait() //wait for the goroutine to finish
}

关于 goroutine 与线程。您不应该混淆它们,并且可能应该了解它们之间的区别。 Goroutines 是绿色线程。关于该主题有无数的博客文章、讲座和 stackoverflow 答案。

【讨论】:

  • 我喜欢等待组的方法,有趣的实现。并感谢您查看 goroutines(绿色线程)与常规线程的指针!
【解决方案2】:

在实现 1 中,range 读入一次通道,然后再次读入 Println。因此,您跳过了 2、4、6、8。

在这两种实现中,一旦最后的 i (9) 被发送到 goroutine,程序就会退出。因此,goroutine 没有时间打印出 9。要解决它,请使用另一个答案中提到的 WaitGroup,或者使用 done 通道来避免信号量/互斥量。

func main() {

    c := make(chan int)
    done := make(chan bool)
    go printchannel(c, done)
    for i:=0; i<10 ; i++ {
        c <- i
    }
    close(c)
    <- done
}

func printchannel(c chan int, done chan bool) {
    for i := range c {
        fmt.Println(i)
    }
    done <- true
}

【讨论】:

    【解决方案3】:

    您的第一个实现只返回每隔一个数字的原因是因为您实际上是在每次循环运行时从c“获取”两次:首先使用range,然后再次使用&lt;-。只是碰巧您实际上并没有绑定或使用从通道中取出的第一个值,因此您最终打印的只是其他所有值。

    第一次实现的另一种方法是根本不使用range,例如:

    func printchannel(c chan int) {
        for {
            fmt.Println(<-c)
        }
    }
    

    我无法在我的机器上复制您的第二个实现的行为,但原因是您的两个实现都是活泼的 - 它们将在 main 结束时终止,无论通道中可能有哪些数据待处理或然而,许多 goroutine 可能是活跃的。

    最后,我警告您不要将 goroutine 视为明确的“线程”,尽管它们具有相似的思维模型和接口。在像这样的简单程序中,Go 完全不可能只使用单个 OS 线程完成所有操作。

    【讨论】:

    • 第二个可能没有一直打印到 9 的原因是主 goroutine 在最后一个值被放入通道后退出,并且接收者 goroutine 可能不打印到那时输出。
    • 添加了一条更新我的答案的注释。
    【解决方案4】:

    您的第一个循环不起作用,因为您有 2 个阻塞通道接收器并且它们不会同时执行。

    当你调用 goroutine 时,循环开始,它等待第一个值被发送到通道。有效地将其视为 &lt;-c

    当 main 函数中的 for 循环运行时,它会在 Chan 上发送 0。此时range c 接收到值并停止阻塞循环的执行。

    然后它被fmt.println(&lt;-c) 的接收者阻止。当在 main 循环的第二次迭代中发送 1 时,在 fmt.println(&lt;-c) 处接收到的消息从通道读取,从而允许 fmt.println 执行,从而完成循环并等待 for range c 处的值。

    您对循环机制的第二个实现是正确的。 它在打印到 9 之前退出的原因是 main 中的 for 循环完成后,程序继续执行并完成 main 的执行。

    在 Go 中, func main 在执行时作为 goroutine 本身启动。因此,当 main 中的 for 循环完成时,它会继续并退出,并且由于 print 位于关闭的并行 goroutine 中,因此它永远不会被执行。没有时间打印它,因为没有任何东西可以阻止 main 完成和退出程序。

    解决此问题的一种方法是使用等待组http://www.golangprograms.com/go-language/concurrency.html

    为了获得预期的结果,您需要在 main 中运行一个阻塞进程,以提供足够的时间或等待确认 goroutine 的执行,然后再允许程序继续。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多