go - 带有通道的 Goroutines 的行为
问题描述
下面给出的代码输出有些令人困惑,请帮助我了解通道和 goroutine 的行为以及实际执行是如何发生的。
我试图理解程序的流程,但是“调用goroutine”之后的语句被执行,即使调用了goroutine,稍后也会执行goroutines中的语句,
在第二个“goroutine 调用”中,行为不同,并且程序的打印/流程顺序发生了变化。
以下是代码:
package main
import "fmt"
func main() {
fmt.Println("1")
done := make(chan string)
go test(done)
fmt.Println("7")
fmt.Println(<-done)
fmt.Println("8")
fmt.Println(<-done)
fmt.Println("9")
fmt.Println(<-done)
}
func test(done chan string) {
fmt.Println("2")
done <- "3"
done <- "10"
fmt.Println("4")
done <- "5"
fmt.Println("6")
}
上述代码的结果:
1
7
2
3
8
10
9
4
6
5
请帮助我理解为什么以及如何得出这个结果。
解决方案
欢迎来到堆栈溢出。我希望我能帮助你更好地理解通道和 goroutines。
概念 1:渠道
将通道可视化为管道,数据从一端进入另一端。输入的第一个数据是从另一侧出来的第一个数据。有缓冲通道和非缓冲通道,但对于您的示例,您只需要了解默认通道,它是无缓冲的。无缓冲通道一次只允许通道中有一个值。
写入无缓冲通道
看起来像这样的代码将数据写入通道的一端。
ch <- value
现在,这段代码实际上等待执行,直到有东西从通道中读取值。无缓冲通道一次只允许一个值在其中,并且在读取之前不会继续执行。稍后我们将看到这如何影响代码执行的顺序。
从无缓冲通道读取
要从无缓冲通道读取(可视化从通道中取出一个值),执行此操作的代码如下所示
[value :=] <-ch
当您阅读代码文档时,[things in] 方括号表示其中的内容是可选的。上面,如果没有 [value :=],您只会从通道中取出一个值,而不会将它用于任何事情。
现在,当通道中有值时,此代码有两个副作用。第一,它在我们现在所处的任何例程中从通道中读取值,然后继续处理该值。它的另一个作用是允许将值放入通道的 goroutine 继续。这是理解您的示例程序所必需的关键部分。
如果通道中还没有值,它将等待一个值写入通道,然后再继续。换句话说,线程会阻塞,直到通道有要读取的值。
概念 2:Goroutines
goroutine 允许您的代码继续同时执行两段代码。这可以用来让你的代码执行得更快,或者同时处理多个问题(想想一个服务器,多个用户同时从它加载页面)。
当您尝试找出在同时执行多个例程时执行代码的顺序时,就会出现您的问题。这是一个很好的问题,其他人已经正确地指出这取决于。当您生成两个 goroutine 时,执行哪些代码行的顺序是任意的。
下面带有 goroutine 的代码可以打印executing a()
或end main()
先打印。这是因为产生一个 gorouting 意味着有两个并发的执行流(线程)同时发生。在这种情况下,一个线程留在main()
其中,另一个开始执行a()
. 运行时如何决定先运行哪个是任意的。
func main() {
fmt.Println("start main()")
go a()
fmt.Println("end main()")
}
func a() {
fmt.Println("executing a()")
}
Goroutines + 通道
现在让我们使用一个通道来控制什么时候执行什么的顺序。现在唯一的区别是我们创建了一个通道,将它传递给 goroutine,然后等待它的值被写入,然后再继续 main。从前面开始,我们讨论了从通道读取值的例程如何需要等到通道中有值才能继续。由于executing a()
总是在写入通道之前打印,我们将始终等待读取放入通道的值,直到executing a()
打印。由于我们在打印之前从通道读取(发生在通道写入之后)end main()
,所以executing a()
总是会在打印之前打印end main()
。我制作了这个游乐场,所以你可以自己运行它。
func main() {
fmt.Println("start main()")
ch := make(chan int)
go a(ch)
<-ch
fmt.Println("end main()")
}
func a(ch chan int) {
fmt.Println("executing a()")
ch <- 0
}
你的例子
我认为在这一点上你可以弄清楚什么时候会发生什么,以及可能以不同的顺序发生什么。当我在脑海中经历它时,我自己的第一次尝试是错误的(参见编辑历史)。你必须要小心!在编辑时我不会给出正确的答案,因为我意识到这可能是一项家庭作业。
编辑:关于更多语义<-done
在我第一次经历时,我忘了提到它fmt.Println(<-done)
在概念上与以下内容相同。
value := <-done
fmt.Println(value)
这很重要,因为它可以帮助您看到当main()
线程从done
通道读取时,它不会同时打印它。这是运行时的两个独立步骤。
我希望这有帮助。如果您有任何问题,请告诉我。
推荐阅读
- ruby-on-rails - 获取没有最近订单的客户
- r - R 在“if”语句后尝试使用“sum”函数时出错(Coursera R 编程课程第 2 周作业)
- java - JavaFX LineChart NullPointer
- sql-server - 解决由于参数嗅探导致的间歇性性能问题
- python - 从恢复的张量流模型中获取“损失”函数
- java - Java Socket 无法使用 NoRouteToHostException 而不是 ConnectionRefused 连接到“0.0.0.0”
- php - 在php中使用ics文件删除日历邀请
- laravel - Laravel - 具有单个 Vue 实例的 Vue 多页面应用程序
- mongoose - 无法更新行 mongoose
- r - 在 R 中生成具有随机选择的特征的数据集列表