概述
通道channel可以被认为是goroutine通信的管道。关于前一篇go语言锁的博客中也提到,解决临界资源问题可以使用锁,但是更建议的是使用channel来实现goroutine之间的通信。
不要通过共享内存来通信,而应该通过通信来共享内存。
- 需要注意是,channel本身就是同步的。意味着同一时间只有一条goroutine来操作channel。
- 死锁——如果一个channel只有读数据,而没有写数据,则会产生死锁。反之亦然。如果写代码时不小心造成了死锁,go语言会抛出相关异常。
channel通道的创建和使用
channel声明
每个通道都有其相关的类型,该类型就是通道内允许传输的数据类型。
空的通道为nil,nil的通道没有任何用处。所以通道的声明是和map类似的。
//声明通道
var channel_name chan type
//初始化通道
channel_name = make(chan type)
简短声明
channel_name := make(chan type)
channel读写操作
data := <- channel_name //将通道中的值赋值给变量
channel_name <- data //将变量的值写入通道
value, ok := <- channel_name //从一个channel中读取值
channel的读写操作都是阻塞的
正常情况下,如果不使用sync.WaitGroup同步等待组、锁,主函数中的子goroutine很可能在还没有执行时,因为主函数的结束而一起结束了。
但是可以通过“channel的读写操作都是阻塞的这一特性”,子goroutine通过channel通道传递信息给主goroutine,主goroutine在读到数据前会一直阻塞。
func main() {
channel1 := make(chan bool)
go func(){
for i:=0;i<10;i++{
fmt.Println(i)
}
channel1 <- true
fmt.Println("子goroutine结束...")
}()
data1 := <- channel1
fmt.Println("channel传递的值:",data1)
fmt.Println("主函数结束...")
}
关闭channel通道
发送者可以通过关闭channel通道,告知接收方不会有更多的数据被发送到channel上了。
close(channel_name)
接受者可以接受来自channel读到数据时,使用额外的变量来检查通道是否关闭。
ok为true时,表示从通道中读取了一个value;当ok是false是,意味着正从一个封闭的通道读取数据,读取到的value都将是零值。
value, ok := <- channel_name
var channel1 = make(chan int)
func main() {
go fun1()
for{
fmt.Println("从通道中读取值...")
v,ok := <-channel1
time.Sleep(1*time.Second)
if !ok {
fmt.Println("channel读取结束,通道关闭",v,ok)
break
}
fmt.Println("通道中的值:",v,ok)
}
}
func fun1() {
for i:=0;i<10;i++{
time.Sleep(1*time.Second)
fmt.Println("通道中写入值:", i)
channel1 <- i
}
close(channel1)
}
channel通道的范围循环
通过for_range增强循环,不需要自己判断通道是否关闭。range会判断通道是否关闭。
var channel1 = make(chan int)
func main() {
go fun1()
for v := range channel1 { // v <- channel1
time.Sleep(1 * time.Second)
fmt.Println(v)
}
}
func fun1() {
for i := 0; i < 10; i++ {
channel1 <- i
}
close(channel1)
}
缓冲通道
带缓冲区的通道,会把数据先写入缓冲区中。发送数据时,只有当缓冲区中满了才会被阻塞;接收数据时,只有缓冲区为空的时候才会被阻塞。
ch := make(chan type, Size)
Size需要大于0,使通道具有缓冲区。默认情况无缓冲区通道的容量为0,所以省略了该参数。
var channel1 = make(chan int,5) //创建一个容量5的缓冲通道
func main() {
go fun1()
for v := range channel1 {
time.Sleep(1 * time.Second)
fmt.Println("\t读出数据",v)
}
}
func fun1() {
for i := 0; i < 10; i++ {
channel1 <- i
fmt.Println("写入数据:",i)
}
close(channel1)
}
定向通道
双向通道
前面我们创建的通道,都是双向通道。双向通道是可以,一个goroutine发送数据,一个goroutine接收数据的。
定向通道(单向通道)
单向通道也就是定向通道。单向通道只能发送或者接收数据。
定向通道创建语法
channel1 := make(chan <-int) //只能写入数据,不能读数据
channel2 := make(<- chan int) //只能读数据,不能写入数据
定向通道的用法
往往创建和使用通道的时候通常还是创建双向通道。单向通道只用来作为函数参数类型。
只是在将通道作为参数传递到函数时,函数中通道的类型使用单向通道。限制通道在函数内部使用时是只能读不能写的;或者是只能写不能读的。