2 回答
TA贡献1827条经验 获得超4个赞
最好不要将其视为“多线程”。Go 为并发提供了直接的便利,而不是为线程提供了便利。它碰巧使用线程来实现其并发性,但这是一个实现细节。参见 Rob Pike 的演讲,Concurrency is not parallelism进行更深入的讨论。
您问题的关键是默认情况下通道是同步的(如果它们在构建过程中没有被缓冲)。当一个 goroutine 写入一个通道时,它会阻塞,直到另一个 goroutine 从该通道读取。所以当这一行执行时:
z := <- d
在此行执行之前,它无法继续:
d <- 0
如果d频道上没有可用的价值,fact则永远不会继续。这对你来说很明显。但反过来也是如此。在从d通道读取某些内容之前,主 goroutine 无法继续。通过这种方式,无缓冲通道提供了跨并发 goroutine 的同步点。
同样,主循环无法继续,直到某个值出现在 上c。我发现使用两个手指并指向每个 goroutine 中的当前代码行很有用。前进一根手指,直到进入频道操作。然后推进另一个,直到它到达一个通道操作。如果您的手指指向同一通道上的读取和写入,则您可以继续。如果他们不是,那么你就陷入了僵局。
如果你仔细考虑一下,你就会发现一个问题。该程序泄漏了一个 goroutine。
func fact(n int, c chan int, d chan int) {
k := /* code to compute factorial of n */
z := <- d // (1)
c <- k + z
d <- z + 1 // (2)
}
在 (2) 处,我们尝试写入d. 什么将允许它继续进行?另一个 goroutine 从d. 请记住,我们启动了Ngoroutines,并且它们都试图从d. 只有其中之一会成功。其他人将在 (1) 处阻塞,等待某些东西出现在 上d。当第一个到达(2)时会发生这种情况。然后那个 goroutine 退出,一个随机的 goroutine 将继续。
但是会有一个最终的 goroutine 永远无法写入d并且会泄漏。为了解决这个问题,需要在 final 之前添加以下内容Printf:
<-d
这将允许最后一个 goroutine 退出。
TA贡献1816条经验 获得超4个赞
如果我们省略主程序中的“d <- 0”行,程序会怎样,为什么?
有了这行代码,每个 goroutine 开始go fact(...)
都会等待来自 channel 的东西,在 statement 阻塞z := <- d
。
该fact()
函数对d
频道的内容没有实际影响- 它删除一些内容并添加一些内容。因此,如果通道中没有任何内容,则不会有任何进展,程序将陷入僵局。
从同一通道读取和写入的 goroutine 要求死锁 - 在现实生活中避免!
如果交换fact过程的前两行,对整个程序的效率有什么影响?
在进行冗长的阶乘计算之前,fact()
例程将等到它从d
通道中获得令牌。
因为d
通道中一次只有一个令牌在播放,这意味着每个 goroutine 只会在收到令牌时进行昂贵的计算,从而有效地将它们序列化。
与最初一样,昂贵的阶乘计算是在等待令牌之前并行完成的。
在实践中,这不会像您希望的那样工作,因为 goroutine 不是预先调度的,仅用于阻塞操作和函数调用。
- 2 回答
- 0 关注
- 191 浏览
添加回答
举报