不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享。这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。
channel 可以看成一个 FIFO 队列,对 FIFO 队列的读写都是原子的操作,不需要加锁。对 channel 的操作行为结果总结如下:
操作 | nil channel | closed channel | not-closed non-nil channel |
---|---|---|---|
close | panic | panic | 成功 close |
写 ch <-
|
一直阻塞 | panic | 阻塞或成功写入数据 |
读 <- ch
|
一直阻塞 | 读取对应类型零值 | 阻塞或成功读取数据 |
读取一个已关闭的 channel 时,总是能读取到对应类型的零值,为了和读取非空未关闭 channel 的行为区别,可以使用两个接收值:
// ok is false when ch is closed
v, ok := <-ch
golang 中大部分类型都是值类型(只有 slice / channel / map 是引用类型),读/写类型是值类型的 channel 时,如果元素 size 比较大时,应该使用指针代替,避免频繁的内存拷贝开销。
两个goroutine通讯
package main
import "fmt"
func SendMessage(ch chan string){
fmt.Println("Start to send message")
ch <- "Hello World"
fmt.Println("Finished!!")
}
func main(){
fmt.Println("Go Channels Tutorial")
values := make(chan string)
defer close(values)
go SendMessage(values)
val := <-values
fmt.Println(val)
}
main方法里创建了一个string类型的Channel,实现主协程与子协程go SendMessage
进行通信。主协程执行到<-values
时发生阻塞,等待读取values
Channel的值,而子协程执行SendMessage
方法时写入values
Channel。即,主协程发生阻塞,等待子协程执行完毕后再继续执行。
执行的结果如下:
~ » go run main.go
Go Channels Tutorial
Start to send message
Finished!!
Hello World
从日志可以看出,打印val语句必须在子协程go SendMessage
执行完成后才执行。
如果在此基础上添加多一个协程写入values
Channel会发生什么?
func SendMessage(ch chan string){
fmt.Println("Start to send message")
time.Sleep(1 * time.Second)
ch <- "Hello World"
fmt.Println("Finished!!")
}
func main(){
fmt.Println("Go Channels Tutorial")
values := make(chan string)
defer close(values)
go SendMessage(values)
go SendMessage(values)
val := <-values
fmt.Println(val)
time.Sleep(3 * time.Second)
}
在主协程中,启动两个子协程给Channel写数据。而在SendMessage
方法里,为了达到显示效果,在写入Channel前先睡眠1秒,在主协程也添加睡眠时间。
执行日志打印如下:
~ » go run main.go
Go Channels Tutorial
Start to send message
Start to send message
Finished!!
Hello World
发现只有其中一个协程完成写入Channel的操作。因为此Channel没有设置缓存,所有只能保存一个写入值。
那么如何才能保证两个子协程能正常写入Channel呢?
有缓冲的Channel
为了保证上面的两个子协程能顺利地把值写入Channel,我们创建一个带缓冲的Channel。
func SendMessage(ch chan string){
fmt.Println("Start to send message")
time.Sleep(1 * time.Second)
ch <- "Hello World"
fmt.Println("Finished!!")
}
func main(){
fmt.Println("Go Channels Tutorial")
values := make(chan string, 2)
defer close(values)
go SendMessage(values)
go SendMessage(values)
val := <-values
fmt.Println(val)
time.Sleep(1 * time.Second)
}
新创建的Channel缓冲两个值,这样就能保证两个子协程能正常写入到Channel中。下面看看打印日志:
~ » go run main.go
Go Channels Tutorial
Start to send message
Start to send message
Finished!!
Finished!!
Hello World
如我们猜想一样,两个子协程都能顺利结束。
晚安~
有疑问加站长微信联系(非本文作者)