go 的創作者在 channel 這塊有提過一個概念
(原) Don't communicate by sharing memory; sharememory by communicating.
(譯) 不要通過共享內存來通信;應該使用通信來共享內存
因此好的 channel 應該是當訊息傳輸完成後,在傳遞的最後給予一個傳遞完成的資訊,類似對講機結束會說一句 over 的概念,那要怎麼實現這個做法呢,請參考以下程式碼,稍微小修改了一下 day13 的程式碼,還不是好的做法,如果想要看看好作法的話,就請繼續往下看:)
// version 1 需要 TimeSleep 的寫法,則會 print 的結果不完整
func worker1(id int, c chan int) {
for n := range c {
fmt.Printf("Worker %d received %c \n", id, n)
}
}
func createWorker1(id int) chan<- int {
c := make(chan int)
go worker1(id, c)
return c
}
func chanDemo1() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
channels[i] = createWorker1(i)
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond) // 刪除?
}
func main() {
chanDemo()
}
輸出結果為:
Worker 5 received f
Worker 2 received c
Worker 3 received d
Worker 4 received e
Worker 0 received a
Worker 6 received g
Worker 1 received b
Worker 6 received G
Worker 1 received B
Worker 2 received C
Worker 3 received D
Worker 4 received E
Worker 5 received F
Worker 8 received i
Worker 7 received h
Worker 7 received H
Worker 8 received I
Worker 9 received j
Worker 9 received J
Worker 0 received A
將 time.Sleep 刪除的話,就會看到不完整的資料,結果為:
Worker 5 received f
Worker 2 received c
Worker 3 received d
Worker 4 received e
Worker 0 received a
Worker 8 received i
Worker 7 received h
Worker 9 received j
Worker 1 received b
Worker 1 received B
Worker 5 received F
Worker 6 received g
Worker 6 received G
那接下來提的方式就是結束後多補上一句 over 的程式碼:
// version 2 不需要 TimeSleep
// 但會按照順序打印,表示是單純按照順序執行,沒有發揮 goroutine 的功能
func doWorker2(id int, c chan int, done chan bool) {
for n := range c {
fmt.Printf("Worker %d received %c \n", id, n)
done <- true
}
}
type worker2 struct {
in chan int
done chan bool
}
func createWorker2(id int) worker2 {
w := worker2{
in: make(chan int),
done: make(chan bool),
}
go doWorker2(id, w.in, w.done)
return w
}
func chanDemo2() {
var workers [10]worker2
for i := 0; i < 10; i++ {
workers[i] = createWorker2(i)
}
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i // send
<-workers[i].done // receive,主要是因為被這行塞住了,所以導致會按順序執行
}
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
<-workers[i].done
}
}
輸出的結果為:
Worker 0 received a
Worker 1 received b
Worker 2 received c
Worker 3 received d
Worker 4 received e
Worker 5 received f
Worker 6 received g
Worker 7 received h
Worker 8 received i
Worker 9 received j
Worker 0 received A
Worker 1 received B
Worker 2 received C
Worker 3 received D
Worker 4 received E
Worker 5 received F
Worker 6 received G
Worker 7 received H
Worker 8 received I
Worker 9 received J
這個方法不需要 time.Sleep 了,但可以看到上面的結果是有排序的,這樣就表示每個執行緒是按照前面的順序完成才排到下一個,這樣就喪失了 goroutine 的效能,因此我們需要再稍微改善一下:
// version 3 資料蒐集完就 print
func doWorker3(id int, c chan int, done chan bool) {
for n := range c {
fmt.Printf("Worker %d received %c \n", id, n)
done <- true
}
}
type worker3 struct {
in chan int
done chan bool
}
func createWorker3(id int) worker3 {
w := worker3{
in: make(chan int),
done: make(chan bool),
}
go doWorker3(id, w.in, w.done)
return w
}
func chanDemo3() {
var workers [10]worker3
for i := 0; i < 10; i++ {
workers[i] = createWorker3(i)
}
for i, worker := range workers {
worker.in <- 'a' + i // send
}
for i := 0; i < 10; i++ {
<-workers[i].done // receive
}
for i, worker := range workers {
worker.in <- 'A' + i
}
for i := 0; i < 10; i++ {
<-workers[i].done // receive
}
}
輸出結果為:
Worker 0 received a
Worker 1 received b
Worker 6 received g
Worker 9 received j
Worker 7 received h
Worker 3 received d
Worker 8 received i
Worker 4 received e
Worker 2 received c
Worker 5 received f
Worker 5 received F
Worker 0 received A
Worker 4 received E
Worker 2 received C
Worker 8 received I
Worker 1 received B
Worker 6 received G
Worker 7 received H
Worker 9 received J
Worker 3 received D
但是這樣感覺還是有點怪,因為還是得大小寫分開,表示還沒辦法將全部資料併行運算,反倒是 version 1 可以,明天看看有沒有比較好的解法,另外 參考來源2 講解得還不錯,可以參考一下
https://github.com/luckyuho/ithome30-golang/tree/main/day14