一、使用channel等待任務結束
在前面的內容中很多地方使用到了:time.Sleep(time.Millisecond)
如:
func chanDemo(){
var channels [10]chan int //建立channel陣列
for i := 0; i < 10; i++{
channels[i] = creatworker(i)
}
for i := 0; i < 10; i++{
channels[i] <- 'a' + 1
}
time.Sleep(time.Millisecond)
}
func bufferedChannel(){
ch := make(chan int, 3)
go worker(0, ch)
for i := 0; i < 10; i++{
ch <- 'a' + i
}
time.Sleep(time.Millisecond)
}
func channelClose(){
ch := make(chan int)
go worker(0, ch)
ch <- 'a'
ch <- 'b'
ch <- 'c'
ch <- 'd'
close(ch)
time.Sleep(time.Millisecond)
}
在這些方法裡面我們很容易知道他們執行所消耗的時間,但事實上,很多程式的時間是不能預估的,我們不能一直是用time包來對程式執行的時間進行預估,是不靠譜的,所以這裡我們有了“使用channel等待任務結束”
先看這段程式碼:
func chanDemo(){
var channels [10]chan int //建立channel陣列
for i := 0; i < 10; i++{
channels[i] = creatworker(i)
}
for i := 0; i < 10; i++{
channels[i] <- 'a' + 1
}
time.Sleep(time.Millisecond)
}
我們需要使用channel併發的列印10個字母,為了讓字母完整列印,我們對程式執行時間進行了預估,讓程式執行1毫秒就結束;下面我們需要使用
1.使用channel等待任務結束
仍然列印字母(列印20個):部分內容見程式碼註釋
使用’chan bool’的通道來共享通訊來使用記憶體,告訴main任務結束
package main
import (
"fmt"
)
//定義一個結構體
//包含一個 'chan int ' 的in和 'chan bool'的done
type Worker struct{
in chan int
done chan bool //對done的接和收作為結束的訊號
}
func DoWork( id int, c chan int, done chan bool){
for {
n := <-c //接受channel的內容
fmt.Printf("worker %d received %c\n", id, n)
done <- true //done接收資料true
}
}
func createWorker(id int) Worker {
//建立Worker的一個物件w
w := Worker{make(chan int),
make(chan bool),
}
go DoWork(id, w.in, w.done) 此處啟動goroutine,即併發
return w
}
func ChanDemo() {
var workers [10]Worker //建立10個抽象型別Worker
for i := 0; i < 10; i++{
workers[i] = createWorker(i) //建立10個Worker的物件,並返回給workers[i]
}
for i := 0; i < 10; i++{ //可使用range
workers[i].in <- 'a' + i //workers[i].in接受資料
}
for _, worker := range workers {
<-worker.done //將done的資料發給mian,告知main該任務結束
}
for i := 0; i < 10; i++{ //可使用range
workers[i].in <- 'A'+ i //workers[i].in接受資料
}
for _, worker := range workers{
<- worker.done //將done的資料發給mian,告知main該任務結束
}
}
func main(){
ChanDemo()
}
列印結果:
worker 0 received a
worker 5 received f
worker 1 received b
worker 6 received g
worker 4 received e
worker 9 received j
worker 8 received i
worker 2 received c
worker 7 received h
worker 3 received d
worker 6 received G
worker 2 received C
worker 3 received D
worker 7 received H
worker 1 received B
worker 4 received E
worker 5 received F
worker 0 received A
worker 9 received J
worker 8 received I
從列印結果看出:是按順序列印的(先小寫後大寫)
這裡還有一種方法:
func ChanDemo() {
var workers [10]Worker
for i := 0; i < 10; i++{
workers[i] = createWorker(i)
}
for i := 0; i < 10; i++{
workers[i].in <- 'a' + i
}
for i := 0; i < 10; i++{
workers[i].in <- 'A'+ i
}
//將兩個<- worker.done 放在一起
for _, worker := range workers{
<- worker.done
<- worker.done
}
}
但是需要注意的是:
我們一共建立了10個goroutine,在第二個for中就已經向所有channel中傳送資料,接著第三個for,又向channel中傳送資料,這樣會死鎖,因為第一次channel中的資料沒有人來接,然後又向channel發資料。
解決方法:在DoWork
函式增加併發,讓done <- true
處於併發執行狀態,可隨時向main發資料。
func DoWork( id int, c chan int, done chan bool){
for {
n := <-c //接受channel的內容
fmt.Printf("worker %d received %c\n", id, n)
go func() {
done <- true
}()
}
}
2.使用系統提供的 ‘WaitGroup’等待任務結束
WaitGroup提供了:Add()、Wait()、Done()方法
package main
import (
"fmt"
"sync")
type Worker struct{
in chan int
wg *sync.WaitGroup //引用需要指標
}
func DoWork( id int, c chan int, wg *sync.WaitGroup){
for {
n := <-c //接受channel的內容
fmt.Printf("worker %d received %c\n", id, n)
wg.Done() //接和收結束資訊
}
}
func createWorker(id int, wg *sync.WaitGroup) Worker {
//建立Worker的一個物件w
w := Worker{make(chan int), wg,}
go DoWork(id, w.in, wg)
return w
}
func ChanDemo() {
var wg sync.WaitGroup
var workers [10]Worker
for i := 0; i < 10; i++{
workers[i] = createWorker(i, &wg) //指標
}
wg.Add(20) //20個任務
for i := 0; i < 10; i++{
workers[i].in <- 'a' + i
}
for i := 0; i < 10; i++{
workers[i].in <- 'A'+ i
}
wg.Wait() //任務結束
}
func main(){
ChanDemo()
}
列印結果:
worker 0 received a
worker 4 received e
worker 6 received g
worker 2 received c
worker 9 received j
worker 7 received h
worker 0 received A
worker 8 received i
worker 5 received f
worker 3 received d
worker 1 received b
worker 1 received B
worker 9 received J
worker 2 received C
worker 3 received D
worker 4 received E
worker 7 received H
worker 8 received I
worker 6 received G
worker 5 received F
二、在這裡總結了幾個常見的問題:
func ChanDemo() {
var workers [10]Worker
for i := 0; i < 10; i++{
workers[i] = createWorker(i)
}
for i := 0; i < 10; i++{
workers[i].in <- 'a' + i
}
在第一個for中,第一步workers[0] = createWorker(0)
然後就進入這裡
func createWorker(id int) Worker {
//建立Worker的一個物件w
w := Worker{make(chan int),
make(chan bool),
}
go DoWork(id, w.in, w.done)
return w
}
在這個函式中我們開了一個goroutine,同時我們會將w返回給workers[0],然後就進入:
for i := 0; i < 10; i++{
workers[i] = createWorker(i)
}
的第二次,第三次迴圈……
直到迴圈結束。
但是這裡就有問題了,在這個途中我們一共開了10 goroutine,但是這10 goroutine都處於等待狀態(因為我們還沒有給channel任何內容,從我們的輸出結果可以看出)
- 那麼這裡的10個goroutine是處於等待狀態是不是因為,我們channel沒有接受到任何資訊,所以就會造成goroutine的等待?
2. 還有這裡:
func DoWork( id int, c chan int, done chan bool){
for {
n := <-c //接受channel的內容
fmt.Printf("id: %v, chan:%c\n", id, n)
done <- true
}
}
這個死迴圈,為什麼在函式呼叫後只迴圈了一遍? 當然這裡我知道他是其中一個goroutine
3. 當然還有一個問題,就是我們在前兩個問題在基礎上,呼叫函式DoWork()時,也會對應的將true傳送給與之對應的workers[i].done中,然後:
for i := 0; i < 10; i++{
workers[i].in <- 'a' + i
}
for _, worker := range workers {
<- worker.done
}
在這裡的第二個for中,這裡<- worker.done全為true,我們是不是從這裡就可以瞭解到前面的10個goroutine結束了?
4. 也正是因為這樣我們才不需要time包,來預計程式的執行時間了?
三、回答
是的,它們此時都在等待,等別人從in中傳送任務資料。
這是個死迴圈,一般我們goroutine中常會這麼寫,只要有任務就做。視訊裡實際上大寫字母,小寫字母,一共執行兩遍。執行多少遍取決於外界,這裡是main函式,到底傳送了多少任務給我這個worker[i]。
這裡的true方向同學搞錯了,是worker通知main函式,說我做完了。<- worker.done這裡是main函式接收worker.done的資料,如果收到,就說明這個worker的事情做完了。
是的,理想情況下應該不需要time包來預計執行時間。預計的時間會不靠譜。
本作品採用《CC 協議》,轉載必須註明作者和本文連結