多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
> channel管道是Go语言推荐的协程之间的通信机制 > channel是一个数据类型,主要用来解决go程的同步问题以及协程之间数据共享(数据传递)的问题 [TOC] ### 创建channel > 语法 ``` channel := make(chan 数据类型) ``` > 例子 ``` // 创建int类型的channel channel := make(chan int) ``` ### 读取channel的数据 > 语法 ~~~ // 从channel读取数据,保存至变量v v := <-channel // 从channel读取数据,数据直接丢弃 <-channel ~~~ > 如果channel没有数据,读取channel会阻塞,同时触发写channel携程 ~~~ package main import ( "fmt" "strconv" "time" ) func collectPersons(n int, name string, c chan string) { for i := 0; i < n; i++ { // 往channel写数据 c <- name + strconv.FormatInt(int64(i), 10) fmt.Println("hello" + strconv.FormatInt(int64(i), 10)) } } func main() { // 定义string类型的channel,缓冲队列大小是10 channel := make(chan string, 5) // 创建一个协程,往channel里写数据 go collectPersons(5, "jiaojiao", channel) fmt.Println("___main111___") time.Sleep(1 * time.Second) // 第1句:读取channel阻塞(因为channel数据为空),同时触发collectPersons()里的channel写数据 fmt.Println(<-channel) fmt.Println("___main222___") fmt.Println(<-channel) } ~~~ > 结果 ~~~ ___main111___ hello0 hello1 hello2 hello3 hello4 jiaojiao0 ___main222___ jiaojiao1 ___main333___ jiaojiao2 ~~~ > 如果channel里根本就没有写入数据,channel读取数据时,则会超时报错,则可以采用`select`来处理超时等待问题 ~~~ package main import ( "fmt" ) func main() { channel := make(chan string, 2) channel <- "haha" fmt.Println(<-channel) // 第2句读取,因为channel没有数据,则会报错,为了防止此类问题,需要用到select fmt.Println(<-channel) } ~~~ ### 往channel写数据 ``` // 往channel变量c中,写入int数据100 channel <- 100 ``` ### channel缓冲区 ~~~ // 有缓冲区channel ch := make(chan int, 100) // 无缓冲区channel ch := make(chan int) //等价于make(chan int, 0) ~~~ #### 无缓冲区channel > 无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道 > 这种类型的通道要求`发送channel`和`接收channel`同时准备好,才能完成发送和接收操作,否则都阻塞等待 > 对通道进行`发送`和`接收`是同步的操作行为。其中任意一个操作都无法离开另一个操作单独存在 ~~~ package main import ( "fmt" "strconv" ) func say(name string, c chan string) { for i := 0; i < 5; i++ { c <- name + strconv.FormatInt(int64(i), 10) fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10)) } } func main() { // 定义一个无缓冲区的channel channel := make(chan string) //channel := make(chan string, 0) go say("jiaojiao", channel) // 由于是无缓冲区channel,每次读取的时候,都会去触发写channel,类似于同步方法 fmt.Println(<-channel) fmt.Println(<-channel) fmt.Println(<-channel) fmt.Println("done") } ~~~ > 结果 > 以下的jiaojiao和hello无所谓谁先执行,因为`无缓冲区的channel`是需要读取和写操作同时准备好了后,才会执行,所以print()信息时候,很有可能有先有后 ~~~ _______hello0 jiaojiao0 ---------------------- jiaojiao1 _______hello1 ---------------------- _______hello2 jiaojiao2 ---------------------- done ~~~ #### 有缓冲区channel > 缓冲区,指的是channel中有一个缓冲队列,只有缓冲区填满后才会阻塞写操作 ~~~ package main import ( "fmt" "strconv" ) func say(name string, c chan string) { for i := 0; i < 5; i++ { c <- name + strconv.FormatInt(int64(i), 10) fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10)) } } func main() { // 定义一个channel,用来数据通讯 channel := make(chan string, 5) go say("jiaojiao", channel) // 第一句读取的时候,读channel阻塞,并且触发写channel,由于channel缓冲区为5,所以写操作时一次性执行了5次 fmt.Println(<-channel) fmt.Println(<-channel) fmt.Println(<-channel) fmt.Println("done") } ~~~ > 结果 ~~~ _______hello0 _______hello1 _______hello2 _______hello3 _______hello4 jiaojiao0 jiaojiao1 jiaojiao2 done ~~~ ### 单向channel > channel默认是双向的,但是我们可以显示的指定他为单向的 ~~~ var writeChannel chan<-int = ch //只能写,不能读 var readChannel <-chan int = ch //只能读,不能写 ~~~ > 例子 ~~~ package main import ( "fmt" ) func producer(out chan<-int) { for i := 0; i <10; i++ { out <- i*i } close(out) } func consumer(in <-chan int) { for num := range in{ fmt.Println("num = ", num) } } func main() { //创建channel channel := make(chan int) //生产者,只能写入channel go producer(channel) //消费者,只能读取消费channel consumer(channel) } ~~~ ### 关闭channel > 只有发送者可以关闭管道,接收者不能关闭管道 ~~~ package main import ( "fmt" ) func main() { //创建channel ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i //写数据 } //不需要写数据了,关闭channel close(ch) }() for { //如果ok为true,说明管道没关闭 if num, ok := <-ch; ok == true { fmt.Println("num = ", num) } else { //管道关闭 break } } } ~~~ ### 遍历channel > 可以使用for语句循环读取channel中的数据 ~~~ package main import ( "fmt" "strconv" ) func collectPersons(n int, name string, c chan string) { for i := 0; i < n; i++ { // 返回当前计算结果 c <- name + strconv.FormatInt(int64(i), 10) fmt.Println("_______hello" + strconv.FormatInt(int64(i), 10)) } // 通过close关闭channel close(c) } func main() { // 定义string类型的channel,缓冲队列大小是3 channel := make(chan string, 3) // 创建一个协程,往channel里写数据 go collectPersons(3, "jiaojiao", channel) // 通过range关键词,循环遍历channel // 如果channel没有数据,就阻塞循环,直到channel中有数据 // 如果channel关闭,则退出循环 for i := range channel { fmt.Println(i) } } ~~~ > 返回结果 ~~~ _______hello0 _______hello1 _______hello2 jiaojiao0 jiaojiao1 jiaojiao2 ~~~ ### select语句 > select语句会阻塞等待多个channel,直到满足条件后执行(如果满足多个条件,随机执行) > select语句中的每个 case 语句里必须是一个 IO 操作 > select本身不带循环,操作需要外层的for ~~~ package main import "fmt" // select模式,写入channel (当然也可改为读取channel,根据自己业务逻辑来) func writeLoop(c, quit chan int) { x := 1 // 开始一个死循环 for { // 通过select等待通道c和quit,看那个有反应,就执行对应的case语句中的代码 select { case c <- x: // 如果通道c写入数据成功,执行这里的计算逻辑 x = x + 1 case <-quit: // 如果收到通道quit的数据,就退出函数,结束计算 fmt.Println("quit") return } } } func main() { // 定义一个channel,用来数据通讯 channel := make(chan int) // 定义一个channel,用来传递停止通知 quit := make(chan int) // 创建一个协程,用来打印计算结果 go func() { // 打印10个计算结果 for i := 0; i < 10; i++ { // 循环从channel通道中读取10次数据,每次读取都触发channel的select模式 fmt.Println(<-channel) } // 往quit通道中发送数据0,通知writeLoop函数退出,主协程就结束了 quit <- 0 }() // 开启channel select模式 writeLoop(channel, quit) fmt.Println("done") } ~~~ > 以上代码使用的是writeLoop(),再举一个readLoop()的例子 ~~~ package main import "fmt" // select模式,读取channel func readLoop(c, quit chan int) { // 开始一个死循环 for { // 通过select等待通道c和quit,看那个有反应,就执行对应的case语句中的代码 select { case x := <-c: // 如果通道c读取数据成功,执行这里的计算逻辑 fmt.Println(x) case <-quit: // 如果收到通道quit的数据,就退出函数 fmt.Println("quit") return } } } func main() { // 定义一个channel,用来数据通讯 channel := make(chan int) // 定义一个channel,用来传递停止通知 quit := make(chan int) // 创建一个协程,用来写channel数据 go func() { for i := 0; i < 10; i++ { // 循环从channel通道中写10次数据,每次写操作都会触发channel的select模式 channel <- i } // 往quit通道中发送数据0,通知readLoop函数退出,主协程就结束了 quit <- 0 }() // 开启channel select模式 readLoop(channel, quit) fmt.Println("done") } ~~~ > 如果select语句有多个分支满足条件,那么它会随机选择一个执行。 ~~~ package main import ( "fmt" ) func main() { channel := make(chan string) go func() { select { // x 和 y都满足条件,会随机执行 case x := <-channel: fmt.Println(x+" from x") case y := <-channel: fmt.Println(y+" from y") default: // 如果有 default 子句,没有满足条件情况下,会执行该语句 // 如果没有 default 子句,select 将阻塞,直到满足条件后执行 fmt.Println("没有满足条件,select语句结束") } }() channel <- "haha" for { ; } } ~~~