2017-04-02 28 views
0

我有一個例子代碼之前sync.WaitGroup結束夠程(你可以找到它的Go Playground):與去年wg.Done()

package main 

import (
    "fmt" 
    "sync" 
    "time" 
) 

func main() { 
    messages := make(chan int) 
    var wg sync.WaitGroup 
    var result []int 

    // you can also add these one at 
    // a time if you need to 

    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 1 
    }() 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 2 
    }() 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 3 
    }() 
    go func() { 
     for i := range messages { 
      fmt.Println(i) 
     result = append(result, i) 
     } 

    }() 

    wg.Wait() 
    fmt.Println(result) 
} 

我得到這樣的輸出:

2 
1 
[2 1] 

我想想我知道爲什麼會發生,但我解決不了。 WaitGroup中有3項我的意思是三個goroutine,第四個groutine使用通道中的數據。當最後一個groutine說wg.Done()由於wg.Wait()說程序結束了,所以每個goroutine都完成了,最後一個goroutine的結果是第四個goroutine不能消耗,因爲程序結束了。我嘗試在第四個函數中添加一個加wg.Add(1)和wg.Done(),但在這種情況下,我得到了死鎖。

+0

請參閱示例http://stackoverflow.com/questions/42085173/channel-deadlock-in-go/42086959#42086959 - 這與您的示例非常相似。 – ain

回答

4

您產卵的最後一條子宮 - 其中一條旨在收集結果 - 不等於main(),所以wg.Wait()在那裏返回,main()退出並收穫殘留的公寓。 假設只有一個單一的收集goroutine保持那個時間,但它無法更新切片。

另外要注意的是,由於你在你的程序中的數據爭同樣的原因:由時間main()讀取結果的切片,它不知道是否安全讀它,也就是說,無論是作家已經寫完了。

一個簡單的解決方法是爲該goroutine添加wg.Add(1),並在其中添加defer wg.Done()

更好的解決方案是在wg.Wait() 之後並且在從片段讀取之前對通道。這將使收集goroutine的range循環終止,這也會在該goroutine和main()之間創建一個適當的同步點。

+0

在這種情況下,我不認爲打個招呼就夠了。即使在關閉之後,讀取通道的goroutine仍然可以在其循環體中,因此仍然能夠修改切片。 – djd

+0

我想kostix提出這個修改:https://play.golang.org/p/A4S5pfd7_O。 – Ingaz

+0

謝謝你的回答。 – PumpkinSeed

1

關閉通道是一種慣用的信號轉換模式,如果關閉了緩衝通道,則使用者可以讀取所有排隊的數據,然後停止。

此代碼工作正常:

func main() { 
    messages := make(chan int) 
    var wg sync.WaitGroup 
    var result []int 

    // you can also add these one at 
    // a time if you need to 

    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 1 
    }() 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 2 
    }() 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     time.Sleep(time.Second * 1) 
     messages <- 3 
    }() 

    // this goroutine added to signal end of data stream 
    // by closing messages channel 
    go func() { 
     wg.Wait() 
     close(messages) 
    }() 

    // if you need this to happen inside a go routine, 
    // this channel is used for signaling end of the work, 
    // also another sync.WaitGroup could be used, but for just one 
    // goroutine, a single channle as a signal makes sense (there is no 
    // groups) 
    done := make(chan struct{}) 
    go func() { 
     defer close(done) 
     for i := range messages { 
      fmt.Println(i) 
      result = append(result, i) 
     } 
    }() 

    <-done 
    fmt.Println(result) 
} 

正如你看到我們剛纔添加另一個夠程封閉的messages通道,當所有的生產商都做了。

+1

感謝您的回答,基本上我做了同樣的事情,然後是前面的評論。 – PumpkinSeed