Go语言强大的goroutine特性,让各位开发者爱不释手,而多个goroutine如何沟通呢?就是透过Channel来做到。本篇教大家从Channel读取数据的两种方式及使用时机,并实际用一个案例快速了解Channel实作上会遇到哪些问题?底下用两个示例让大家了解如何读取Channel数据出来。
读取Channel两种方式
第一个用的是方式 for range
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
第二种是透过方式 v,ok:= <-ch
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
for {
v, ok := <-ch
if !ok {
return
}
fmt.Println(v)
}
}
看完上面两个示例,开发者也很清楚知道这两种读取方式,但是会遇到什么时候开始用第一种,什么时候该使用第二种?底下来看看一个简单示例
两个goroutine交互读取字元
先看看题目,有一个字串foobar,将字元拆开丢到Channel内,用两个goroutine交互读取字元,底下是最后的输出结果
goroutine01: f
goroutine02: o
goroutine01: o
goroutine02: b
goroutine01: a
goroutine02: r
先把上面题目复制到,大家可以看一下底下示例后,看看怎么写出两个goroutine,以下示例解答:
package main
import (
"sync"
)
func main() {
str := []byte("foobar")
ch := make(chan byte, len(str))
wg := &sync.WaitGroup{}
wg.Add(2)
for i := 0; i < len(str); i++ {
ch <- str[i]
}
go func() {
}()
go func() {
}()
wg.Wait()
}
看完这题目,大家应该就知道是无法使用方式一来读取channel数据,因为会持续读数据直到channel被关闭为止,这样是不能保证另一个gorountine可以正确读到下一个字元。for range
实作方式
从上面示例可以看到两个goroutine里面写的代码应该要一样,故需要一个channel来通知下一个goroutine进行读取,将代码改成如下:
package main
import (
"fmt"
"sync"
)
func main() {
str := []byte("foobar")
ch := make(chan byte, len(str))
next := make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(2)
for i := 0; i < len(str); i++ {
ch <- str[i]
}
close(ch)
go func() {
defer wg.Done()
for {
<-next
v, ok := <-ch
if ok {
fmt.Println("goroutine01:", string(v))
} else {
close(next)
return
}
next <- struct{}{}
}
}()
go func() {
defer wg.Done()
for {
<-next
v, ok := <-ch
if ok {
fmt.Println("goroutine02:", string(v))
} else {
close(next)
return
}
next <- struct{}{}
}
}()
next <- struct{}{}
wg.Wait()
}
- 首先当数据全部写进Channel后,需要关闭Channel
- 新增next Channel用来通知下一个goroutine读取数据
- main主函式要先丢数据到next Channel
- 当ch Channel读取数据结束后,需要关闭next Channel
执行完上述步骤后,会得到底下结果
goroutine02: f
goroutine01: o
goroutine02: o
goroutine01: b
goroutine02: a
goroutine01: r
panic: close of closed channel
这边可以看到此channel被关闭后,会一直有数据,故需要用另一种方式来判断channel是否关闭,就改成如下<-next
go func() {
defer wg.Done()
for {
stop, ok := <-next
if !ok {
return
}
v, ok := <-ch
if ok {
fmt.Println("goroutine01:", string(v))
} else {
close(next)
return
}
next <- stop
}
}()
程序可以正确执行了,但是看到代码,我们可以在重构一次if else
go func() {
defer wg.Done()
for {
stop, ok := <-next
if !ok {
return
}
v, ok := <-ch
if !ok {
close(next)
return
}
fmt.Println("goroutine01:", string(v))
next <- stop
}
}()
最后完整代码如下
package main
import (
"fmt"
"sync"
)
func main() {
str := []byte("foobar")
ch := make(chan byte, len(str))
next := make(chan struct{})
wg := &sync.WaitGroup{}
wg.Add(2)
for i := 0; i < len(str); i++ {
ch <- str[i]
}
close(ch)
go func() {
defer wg.Done()
for {
stop, ok := <-next
if !ok {
return
}
v, ok := <-ch
if !ok {
close(next)
return
}
fmt.Println("goroutine01:", string(v))
next <- stop
}
}()
go func() {
defer wg.Done()
for {
stop, ok := <-next
if !ok {
return
}
v, ok := <-ch
if !ok {
close(next)
return
}
fmt.Println("goroutine02:", string(v))
next <- stop
}
}()
next <- struct{}{}
wg.Wait()
}
心得
透过上述示例希望可以让刚入门朋友了解Channel特性,除了此案例之外,大家可以想一下怎么实现worker pool pattern,之后有机会可以跟大家怎么介绍此部分