我在模拟考试中得到了两个问题.我得到了答案,但无法弄清楚背后的理由.
我将首先发布代码,然后发布问题和答案.也许有人会这么好解释我的答案?
package main import "fmt" func fact(n int, c chan int, d chan int) { k := /* code to compute factorial of n */ z := <- d c <- k + z d <- z + 1 } func main() { r := 0 c := make(chan int) d := make(chan int) for i = 0 ; i < N ; i++ { go fact(i,c,d) } d <- 0 for j = 0 ; j < N ; j++ { r = r + <-c } fmt.Printf("result = %d\n",r) }
第一个问题是:
如果在主程序中省略"d < - 0"行,程序如何表现,为什么?
老师的答案是:
所有线程的状态都被阻塞,也是主线程.
第二个问题是:
如果我们交换事实程序的前两行,整个计划的效率会如何影响?
答案是:
所有线程都将按顺序运行.每个线程只有在完成时才会触发另一个线程.
最好不要将其视为"多线程".Go为并发提供直接设施,而不是线程.它恰好通过线程实现其并发性,但这是一个实现细节.请参阅Rob Pike的演讲,对于更深入的讨论,并发不是并行性.
您的问题的关键是默认情况下通道是同步的(如果它们在构造期间没有被缓冲).当一个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
.记住,我们开始使用N
goroutines,并且所有人都试图阅读d
.只有其中一个会成功.其他人将阻止(1),等待出现的东西d
.当第一个到达(2)时会发生这种情况.然后那个goroutine退出,随机goroutine将继续.
但是会有一个永远无法写入的最终goroutine,d
它会泄漏.为了解决这个问题,需要在最终决定之前添加以下内容Printf
:
<-d
这将允许最后一个goroutine退出.