简介
说道通道:channel,不得不提Go语言的并发编程模型:CSP。
Go语言提倡,通过通信来共享内存。
Goroutines:可以看做是Go的一个并发线程。基于系统线程。
每启动一个Goroutine的堆初始化占用是2/4K,可以扩大到1G。这也是Go语言可以启动成千上万个Goroutine的原因。
启动一个Goroutine只需要再函数调用前加一个关键字go :go myfunc()
通道channel就是作为这些Goroutine之间相互通信、共享数据的渠道,准确的说就是通道。
channel是引用类型,多个名字指向一个channel实例是,一个关闭全部关闭。
channel分为双向通道、只读通道、只写通道。可以通过close()函数关闭通道。
关闭的通道只可以读不可以写(只写的不可读)。
channel带缓冲区和不带缓冲区的区别:
不带缓冲区写入一个在写入就会阻塞。带缓冲区就是比不带缓冲区的多写带的缓冲区大小个数的数据,再写也会阻塞。
使用
- 创建channel
var ch1 chan int
创建的ch1是nil值,读取会阻塞,写入会阻塞,close()会panic。除非特殊需求,不要这样创建channel;
推荐使用make()创建channel。
//双向channel
var ch1=make(chan int)
//只读
var ch2=make(<-chan int)
//只写
var ch3=make(chan int<-)
package main
import (
"fmt"
)
func main() {
// 双向不带缓冲区
var ch1 =make(chan int)
// 正常的channel读取写入时,读空会阻塞,写满会阻塞,close()后可读不可写(写close的chanel会panic)。读空会得到0值。
go func(ch chan int){
for i:=0;i<10;i++{
ch<-i
}
close(ch)
}(ch1)
for{
fmt.Println("res:",<-ch1)
}
}
=>
这里打印外0~9,会一直打印0,因为ch1被close了,<-ch1读取到的返回值一直是0
这里打印外0~9,会一直打印0,因为ch1被close了,<-ch1读取到的返回值一直是0。
- 使用_,ok判断channel是否关闭
上面的问题怎么改正?使用ok布尔值判断是否关闭。
读已关闭的channel会得到零值,如果不确定channel,需要使用ok进行检测。ok的结果和含义:
true:读到数据,并且通道没有关闭。
false:通道关闭,无数据读到。
for{
if res,ok:=<-ch1;ok{
fmt.Println("res:",res)
}else{
break
}
}
=>
res: 0
res: 1
res: 2
res: 3
res: 4
res: 5
res: 6
res: 7
res: 8
res: 9
正常退出。
- 使用for range读channel
使用for range读取channel,读完也会退出。
场景:当需要不断从channel读取数据时。
使用for-range读取channel,这样既安全又便利,当channel关闭时,for循环会自动退出,无需主动监测channel是否关闭,可以防止读取已经关闭的channel,造成读到数据为通道所存储的数据类型的零值。
for res:=range ch1{
fmt.Println("res:",res)
}
- select处理多个channel
select可以同时监控多个通道的情况,只处理未阻塞的case。当通道为nil时,对应的case永远为阻塞,无论读写。特殊关注:普通情况下,对nil的通道写操作是要panic的。
for{
select{
case res1:=<-ch1:
fmt.Println("res1:",res1)
case res2:=<-ch2:
fmt.Println("res2:",res2)
default:
fmt.Println("break")
break
}
}
- select中使用超时
for{
select{
case res1:=<-ch1:
fmt.Println("res1:",res1)
case <-time.After(time.Second):
break
}
}
- 只读只写通道的作用
双向通道可以隐身转换为只读或者只写通道。但是只读或者只写通道不能转换为双向或别的通道。
只读通道可以在只需要读写的函数内使用,防止函数内部误写入。只写也是如此。
package main
import (
"fmt"
"time"
)
func main() {
var ch1=make(chan int)
go func(ch chan<- int){
for i:=0;i<10;i++{
ch<-i
}
}(ch1)
go func(ch <-chan int){
for res:=range ch{
fmt.Println("res:",res)
}
}(ch1)
time.Sleep(10*time.Second)
}
=》
res: 0
res: 1
res: 2
res: 3
res: 4
res: 5
res: 6
res: 7
res: 8
res: 9
- 使用带缓存的channel控制并发
package main
import (
"time"
"fmt"
)
func Process(ch chan int) {
//Do some work...
time.Sleep(time.Second)
ch <- 1 //管道中写入一个元素表示当前协程已结束
}
func main() {
channels := make([]chan int, 10) //创建一个10个元素的切片,元素类型为channel
for i:= 0; i < 10; i++ {
channels[i] = make(chan int) //切片中放入一个channel
go Process(channels[i]) //启动协程,传一个管道用于通信
}
for i, ch := range channels { //遍历切片,等待子协程结束
<-ch
fmt.Println("Routine ", i, " quit!")
}
}
- channel做同步,最简单的生产者消费者
package main
// 带缓冲区的channel
import (
"fmt"
"time"
)
func produce(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("Send:", i)
}
}
func consumer(ch <-chan int) {
for i := 0; i < 10; i++ {
v := <-ch
fmt.Println("Receive:", v)
}
}
func main() {
ch := make(chan int, 10)
go produce(ch)
go consumer(ch)
time.Sleep(1 * time.Second)
}