2015-12-30 34 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沒有預先安排,只能在阻塞操作和函數調用的情況下,這種方法的效果不如預期好。

相關問題