2015-12-30 33 views
3

我有两个模拟考试的问题。我得到了答案,但无法弄清楚他们背后的理由。Go中的多线程。有人可以向我解释这些答案吗?

我会先发布代码,然后回答问题和答案。也许有人会如此善意地向我解释答案?

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”的主要程序,为什么?

从老师的答案是:

所有线程的状态被封锁,同时主线程。

第二个问题是:

会如何,如果我们换一个事实过程的前两行的整个程序的效率会受到影响?

答案是:

所有线程都将按顺序运行。每个线程只有在完成时才会触发另一个线程。

回答

10

最好不要把它想成“多线程”。 Go提供直接的并发性设施,而不是线程。它恰好实现了线程的并发性,但这是一个实现细节。请参阅Rob Pike的演讲,Concurrency is not parallelism进行更深入的讨论。

您的问题的关键在于,通道默认是同步的(如果它们在构造过程中没有被缓冲)。当一个goroutine写入一个通道时,它会阻塞,直到其他某个goroutine从该通道读取。所以,当这行执行:

z := <- d 

它不能继续,直到该行执行:

d <- 0 

如果没有一定的价值在d频道可用,fact永远不会继续进行。这对你来说可能很明显。 但反过来也是如此。在从d频道读取某些内容之前,主要的常规无法继续。通过这种方式,无缓冲的通道在并发的goroutine之间提供了一个同步点。

同样,主循环不能继续,直到某些值出现在c上。我发现使用两个手指并指向每个goroutine中的当前代码行非常有用。前进一个手指,直到您进入频道操作。然后推进另一个,直到达到频道操作。如果您的手指指向同一通道上的读取和写入,则可以继续。如果他们不是,那么你陷入了僵局。

如果您认为这一点,您会发现一个问题。这个程序泄漏了一个goroutine。 (2)我们尝试写入d。(2)我们试着写信给d。什么将允许继续?从d读取另一个goroutine。请记住,我们开始N goroutines,他们都试图从d读取。其中只有一个会成功。其他人将在(1)处阻塞,等待在d上显示的内容。当第一个人到达(2)时会发生这种情况。然后该门厅退出,随机的门厅将继续进行。

但是会有最后的goroutine,永远不能写入d,它会泄漏。为了解决,下面就需要最终Printf之前加入:

<-d 

这将使最后的够程退出。

4

如果我们在主程序中省略了行“d <-0”,程序如何行为?为什么?

Withough这条线,每个够程由go fact(...)开始将等待来自通道,挡在声明z := <- d东西。

fact()函数对d通道的内容没有任何影响 - 它删除了某些内容并添加了一些内容。因此,如果频道中没有任何内容,就没有进展,程序将会陷入僵局。

从同一频道读取和写入的goroutine正在寻求死锁 - 避免在现实生活中!

如果我们交换事实过程的前两行,整个程序的效率会受到怎样的影响?

fact()程序将等待,直到它做了冗长的阶乘计算前获得一个令牌从d通道。

因为在d通道中只有一个令牌正在播放,这意味着每个去程序只会在接收到令牌时进行昂贵的计算,从而有效地序列化它们。

就像它最初一样,昂贵的因子计算是在等待令牌之前并行完成的。

实际上,由于goroutine没有预先安排,只能在阻塞操作和函数调用的情况下,这种方法的效果不如预期好。

相关问题