如何在 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 概念,指向这些概念的指针会有所帮助。
非常感谢对此的反馈(以及我对 goroutine 中通道操作的理解)!
您的第一个实施仅 returns 每隔一个数字的原因是因为每次循环运行时您实际上 "taking" 来自 c
两次:首先是 range
,然后再次使用 <-
。碰巧你实际上并没有绑定或使用从通道中取出的第一个值,所以你最终打印的是所有其他值。
第一个实现的另一种方法是根本不使用 range
,例如:
func printchannel(c chan int) {
for {
fmt.Println(<-c)
}
}
我无法在我的机器上复制你的第二个实现的行为,但原因是你的两个实现都是活泼的——它们将在 main 结束时终止,无论什么数据可能在等待中channel 或许多 goroutines 可能处于活动状态。
作为结束语,我警告您不要将 goroutine 明确视为 "threads",尽管它们具有相似的思维模型和界面。在像这样的简单程序中,Go 完全可能只使用单个 OS 线程来完成所有工作。
您的第一个循环不起作用,因为您有 2 个阻塞通道接收器并且它们不会同时执行。
当您调用 goroutine 时,循环开始,它等待第一个值被发送到通道。有效地将其视为 <-c
。
当 main 函数中的 for 循环运行时,它在 Chan 上发送 0。此时 range c
收到值并停止阻止循环的执行。
然后它被 fmt.println(<-c)
的接收方阻止。当 1 在 main 中的循环的第二次迭代中发送时,在 fmt.println(<-c)
处接收到的数据会从通道读取,从而允许 fmt.println
执行,从而完成循环并等待 for range c
处的值.
你对循环机制的第二种实现是正确的。
它在打印到 9 之前退出的原因是在 main
中的 for 循环完成后程序继续并完成 main.
的执行
在 Go 中,func main 在执行时作为 goroutine 本身启动。因此,当 main 中的 for 循环完成时,它会继续并退出,并且由于打印在一个关闭的并行 goroutine 中,因此它永远不会执行。它没有时间打印,因为没有什么可以阻止 main 完成和退出程序。
解决这个问题的一种方法是使用等待组http://www.golangprograms.com/go-language/concurrency.html
为了获得预期的结果,您需要在 main 中有一个阻塞进程 运行 以提供足够的时间或等待 goroutine 执行的确认,然后才允许程序继续。
实施 1. 您从频道读取了两次 - range c
和 <-c
都从频道读取。
实施2。这是正确的做法。您可能看不到 9 打印的原因是两个 goroutines 可能 运行 在并行线程中。在那种情况下,它可能是这样的:
- main goroutine 将 9 发送到通道并阻塞直到它被读取
- 第二个 goroutine 从通道接收 9
- 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
}
关于 goroutines 与线程。你不应该混淆它们并且可能应该理解它们之间的区别。 Goroutines 是绿色线程。关于该主题的博客文章、讲座和 Whosebug 答案数不胜数。
在实现 1 中,range 读入通道一次,然后再次读入 Println。因此,您将跳过 2、4、6、8。
在这两种实现中,一旦最后的 i (9) 被发送到 goroutine,程序就会退出。因此 goroutine 没有时间打印出 9。要解决这个问题,请使用另一个答案中提到的 WaitGroup,或者使用 done 通道来避免 semaphore/mutex.
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
}
我是新手,我想了解 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 概念,指向这些概念的指针会有所帮助。
非常感谢对此的反馈(以及我对 goroutine 中通道操作的理解)!
您的第一个实施仅 returns 每隔一个数字的原因是因为每次循环运行时您实际上 "taking" 来自 c
两次:首先是 range
,然后再次使用 <-
。碰巧你实际上并没有绑定或使用从通道中取出的第一个值,所以你最终打印的是所有其他值。
第一个实现的另一种方法是根本不使用 range
,例如:
func printchannel(c chan int) {
for {
fmt.Println(<-c)
}
}
我无法在我的机器上复制你的第二个实现的行为,但原因是你的两个实现都是活泼的——它们将在 main 结束时终止,无论什么数据可能在等待中channel 或许多 goroutines 可能处于活动状态。
作为结束语,我警告您不要将 goroutine 明确视为 "threads",尽管它们具有相似的思维模型和界面。在像这样的简单程序中,Go 完全可能只使用单个 OS 线程来完成所有工作。
您的第一个循环不起作用,因为您有 2 个阻塞通道接收器并且它们不会同时执行。
当您调用 goroutine 时,循环开始,它等待第一个值被发送到通道。有效地将其视为 <-c
。
当 main 函数中的 for 循环运行时,它在 Chan 上发送 0。此时 range c
收到值并停止阻止循环的执行。
然后它被 fmt.println(<-c)
的接收方阻止。当 1 在 main 中的循环的第二次迭代中发送时,在 fmt.println(<-c)
处接收到的数据会从通道读取,从而允许 fmt.println
执行,从而完成循环并等待 for range c
处的值.
你对循环机制的第二种实现是正确的。
它在打印到 9 之前退出的原因是在 main
中的 for 循环完成后程序继续并完成 main.
在 Go 中,func main 在执行时作为 goroutine 本身启动。因此,当 main 中的 for 循环完成时,它会继续并退出,并且由于打印在一个关闭的并行 goroutine 中,因此它永远不会执行。它没有时间打印,因为没有什么可以阻止 main 完成和退出程序。
解决这个问题的一种方法是使用等待组http://www.golangprograms.com/go-language/concurrency.html
为了获得预期的结果,您需要在 main 中有一个阻塞进程 运行 以提供足够的时间或等待 goroutine 执行的确认,然后才允许程序继续。
实施 1. 您从频道读取了两次 - range c
和 <-c
都从频道读取。
实施2。这是正确的做法。您可能看不到 9 打印的原因是两个 goroutines 可能 运行 在并行线程中。在那种情况下,它可能是这样的:
- main goroutine 将 9 发送到通道并阻塞直到它被读取
- 第二个 goroutine 从通道接收 9
- 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
}
关于 goroutines 与线程。你不应该混淆它们并且可能应该理解它们之间的区别。 Goroutines 是绿色线程。关于该主题的博客文章、讲座和 Whosebug 答案数不胜数。
在实现 1 中,range 读入通道一次,然后再次读入 Println。因此,您将跳过 2、4、6、8。
在这两种实现中,一旦最后的 i (9) 被发送到 goroutine,程序就会退出。因此 goroutine 没有时间打印出 9。要解决这个问题,请使用另一个答案中提到的 WaitGroup,或者使用 done 通道来避免 semaphore/mutex.
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
}