繁体   English   中英

如何在Golang中与goroutines中的通道安全交互

[英]How to safely interact with channels in goroutines in Golang

我是新手,我正在尝试了解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中的通道操作的理解)非常感谢!

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

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

  1. 主goroutine将9发送到通道并阻塞直到被读取
  2. 第二个goroutine从通道接收9
  3. 主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
}

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

您的第一个实现仅返回其他所有数字的原因是,您实际上每次循环运行都会从c “获取”两次:首先使用range ,然后使用<- 碰巧您实际上并没有绑定或使用通道上取下的第一个值,因此最终您只能进行打印。

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

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

我无法在我的机器上复制第二个实现的行为,但是原因是这两个实现都是正常的-无论何时通道中有待处理的数据或有很多goroutine,它们都会在主端结束时终止可能是活跃的。

作为结束语,我警告您不要将goroutines明确地视为“线程”,尽管它们具有类似的思维模型和接口。 在像这样的简单程序中,Go完全不可能只使用一个OS线程来完成全部操作。

在实现1中,范围一次读入通道,然后在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
}

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM