基础
- channel类似于c中的pipe,管道
- 解决协程同步问题
- 解决数据共享问题
- goroutine核心思想是:通过通信来共享内存,而不是共享内存来通信
语法
- 语法
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity)
- make创建,返回的是一个引用
- 当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。
- 读写 channel
channel <- value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同时检查通道是否已关闭或者是否为空
channel的缓冲类型
阻塞:由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足,才接触阻塞。
同步:在两个或多个协程(线程)间,保持数据内容一致性的机制。
无缓冲channel
- 无缓冲的通道(unbuffered channel)
- 这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 0) //创建无缓冲的通道 c
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子协程结束") //主协程结束时子协程还未结束,所以此句话没有打印出来
for i := 0; i < 3; i++ {
c <- i //子协程向管道写入后,将阻塞,主协程抢到cpu
fmt.Printf("子协程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延时2s,此时子协程将抢到cpu
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
fmt.Println("main协程结束")
}
缓冲channel
- 有缓冲的通道(buffered channel)
- 通道空时读阻塞,通道满时写阻塞
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3)
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子协程结束")
for i := 0; i < 3; i++ { //由于管道数量是3,向管道里连写3次数据
c <- i
fmt.Printf("子协程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延时2s,子协程抢到cpu
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
fmt.Println("main协程结束")
}
关闭管道
- 结束range循环需要关闭channel;
- 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
- 关闭channel后,可以继续从channel接收数据;
- 对于nil channel,无论收发都会被阻塞。
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)//把 close(c) 注释掉,会一直阻塞在 for data := range c 那一行,因为没有刻度的
}()
for data := range c {
fmt.Println(data)
}
fmt.Println("Finished")
}
单向管道
- chan<- 表示数据进入管道
- <-chan 表示数据从管道出来
- 可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通 channel:
c := make(chan int, 3)
var send chan<- int = c // send-only
var recv <-chan int = c // receive-only
send <- 1
同步
package main
import (
"fmt"
"time"
)
var channel = make(chan int) // 全局定义channel, 用来完成数据同步
func printer(s string) {
for _, ch := range s {
fmt.Printf("%c", ch)
time.Sleep(300 * time.Millisecond)
}
}
func person1() { // person1 先执行,中间sleep时会切换到person2,但person2取channel会阻塞,这期间person1又会抢回cpu
printer("hello")
channel <- 1
}
func person2() { // person2 后执行
<-channel
printer("wrold")
}
func main() {
go person1()
go person2()
for {
}
}