如何安全地与Golang中的goroutine中的频道进行交互

时间:2018-04-22 22:13:12

标签: multithreading go range goroutine channels

我是新手,我正在努力了解goroutines中频道的工作方式。据我所知,关键字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概念,指向它会有所帮助。

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

4 个答案:

答案 0 :(得分:3)

实施1.您已经从频道两次阅读 - range c<-c都在从频道中读取。

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

  1. 主goroutine向频道发送9并阻止直到它被阅读
  2. 第二个goroutine从频道收到9
  3. 主要goroutine解锁和退出。这终止了整个程序,它没有给第二个goroutine一个机会打印9
  4. 如果你需要同步你的goroutines。例如,像这样

    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
    }
    

    关于goroutines vs threads。你不应该混淆它们,也许应该理解它们之间的区别。 Goroutines是绿线。有关该主题的无数博客文章,讲座和stackoverflow答案。

答案 1 :(得分:1)

你的第一次实施只返回其他所有数字的原因是因为你实际上是&#34;采取&#34;每次循环运行时c两次:首先使用range,然后再使用<-。碰巧你实际上没有绑定或使用从频道取下的第一个值,所以你最后打印的都是其他的。

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

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

我无法在我的机器上复制你的第二个实现的行为,但原因是你的两个实现都很活泼 - 它们将在主要结束时终止,无论通道中有哪些数据可能未决或者然而,许多goroutines可能是活跃的。

作为结束语,我警告你不要将goroutines视为明确存在&#34;线程&#34;,尽管他们有类似的心理模型和界面。在这样一个简单的程序中,Go可能不会使用单个OS线程完成所有操作。

答案 2 :(得分:1)

在实现1中,范围一次读入通道,然后再在Println中读取。因此,您正在跳过2,4,6,8。

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

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 :(得分:-1)

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

当您调用goroutine时,循环开始,并等待第一个值发送到通道。有效地将其视为<-c

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

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

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

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

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

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