一、使用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
二、select
之前的內容中,我們使用channel都是一個一個的收資料,如果我們需要把多個channel同時收,該怎麼辦?
答案是:Go語言引入了select語句
下面來具體介紹一下select:
select的邏輯和switch的邏輯類似,他們都有多個case分支和default,但select是針對channel的,其邏輯是:在多個含有case分支的select裡面,當某時刻相應的channel滿足發發出資料,讓外面接收,就能滿足對應case,接下來就會執行該case對應的語句塊,如果多個case同時都滿足條件,則會隨機選擇其中一個case,如果所有case都不滿足則會執行default
例如:var activeWorker chan<- int n := 0 select { //c1, c2 為chan int型別 case n = <-c1: fmt.Printf("this is c1:%d\n", n) case n = <-c2: fmt.Printf("this is c2:%d\n", n) case activeWorker <- n: hasValue = false default: fmt.Println("not find channel") return }
在執行select時,程式會將所有的case分析一遍,先來看第一個case,如果此時c1發出資料,則第一個case可被執行,再看第二個case,如果此時c2發出資料,則第二個case可被執行,再看第三個case,如果此時n有值就會將其值發給activeWorker,最後來看default,當上面所有的case都不滿足時,就會執行default的語句塊。
下面來看一個完整的select的應用:
package main
import (
"fmt"
"math/rand" "time")
//控制時間,向channel裡面傳送訊息
func generator() chan int{
out := make(chan int)
go func() {
i := 0
for{
//控制傳送資料時間間隔
time.Sleep(time.Duration( rand.Intn(1500) ) * time.Microsecond)
out <- i
i++
}
}()
return out
}
//channel接受和列印資訊
func DoWork( id int, c chan int){
for {
n := <- c //接受channel的內容
time.Sleep(time.Second) //控制列印時間間隔
fmt.Printf("worker %d received %d\n", id, n)
}
}
//建立channel,啟動併發
func createWorker(id int) chan<- int{
w := make(chan int)
go DoWork(id, w)
return w
}
func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
n := 0
var Values []int //動態快取資料
//tm為程式總時間 tm := time.After(1 * time.Second)
tick := time.Tick(time.Second)
for{
var activeWorker chan<- int
var activeValue int
if len(Values) > 0 {
activeWorker = worker
activeValue = Values[0]
}
select {
case n = <-c1:
Values = append(Values, n)
case n = <-c2:
Values = append(Values, n)
case activeWorker <- activeValue:
Values = Values[1:]
case <- time.After(2000 * time.Microsecond): //每兩次傳送資料時間差超過800毫秒執行一次
fmt.Println("timeout")
case <- tick: //使用tick反映系統狀態
fmt.Println("queue len is:",len(Values))
case <- tm: //使用tm控制總時間
fmt.Println("bey")
return
}
}
}
列印結果:
worker 0 received 132
worker 0 received 133
worker 0 received 134
worker 0 received 136
worker 0 received 135
worker 0 received 136
worker 0 received 137
worker 0 received 138
worker 0 received 137
worker 0 received 138
worker 0 received 139
timeout
worker 0 received 140
worker 0 received 139
timeout
worker 0 received 140
worker 0 received 141
worker 0 received 142
worker 0 received 143
worker 0 received 141
timeout
worker 0 received 142
worker 0 received 144
worker 0 received 145
worker 0 received 143
worker 0 received 144
worker 0 received 145
worker 0 received 146
worker 0 received 146
worker 0 received 147
worker 0 received 147
worker 0 received 148
worker 0 received 148
worker 0 received 149
worker 0 received 149
worker 0 received 150
worker 0 received 151
timeout
worker 0 received 152
worker 0 received 150
worker 0 received 153
worker 0 received 151
worker 0 received 152
worker 0 received 153
worker 0 received 154
worker 0 received 154
timeout
worker 0 received 155
worker 0 received 156
worker 0 received 155
worker 0 received 157
worker 0 received 156
worker 0 received 158
worker 0 received 157
timeout
worker 0 received 158
worker 0 received 159
worker 0 received 159
worker 0 received 160
worker 0 received 161
worker 0 received 162
worker 0 received 160
timeout
worker 0 received 161
worker 0 received 163
worker 0 received 162
timeout
worker 0 received 164
worker 0 received 163
worker 0 received 164
worker 0 received 165
worker 0 received 166
worker 0 received 167
worker 0 received 165
worker 0 received 168
timeout
worker 0 received 166
worker 0 received 169
worker 0 received 167
worker 0 received 170
timeout
worker 0 received 171
worker 0 received 168
timeout
worker 0 received 172
worker 0 received 169
worker 0 received 170
worker 0 received 171
worker 0 received 173
timeout
worker 0 received 174
worker 0 received 172
worker 0 received 175
……
……
……
worker 0 received 2352
worker 0 received 2386
timeout
worker 0 received 2387
worker 0 received 2353
timeout
worker 0 received 2388
worker 0 received 2389
worker 0 received 2354
worker 0 received 2390
worker 0 received 2355
bey
這個程式充分體現了select的實際應用
三、在這裡總結了幾個常見的問題:
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 協議》,轉載必須註明作者和本文連結