[英]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可能在并行线程中运行。 在这种情况下,它可能像这样:
在这种情况下,您必须同步您的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.