公司动态
GO_并发编程---channel
前置回顾Go 并发核心设计理念不要通过共享内存来通信而要通过通信来共享内存goroutine之间数据同步、数据交互统一依靠channel实现解决多协程资源竞争问题。一、Channel 基础概念作用实现多个 goroutine 同步协程之间安全共享、传递数据底层理解可以把 channel 看作一根管道 / 水管两端分别对应发送、接收无缓存管道一端没就绪另一端会阻塞有缓存管道缓冲区存数据缓冲区满 / 空时发生阻塞。传递特性channel 本身是地址传递引用类型不是值传递。二、Channel 创建语法使用内置函数make创建分两种go// 1. 无缓冲通道容量为0 ch : make(chan 数据类型) ch : make(chan int, 0) // 2. 有缓冲通道capacity代表缓冲区最大存储数量 ch : make(chan string, 5)三、Channel 收发操作符--是通道专属操作符四种常用写法go// 1. 发送数据把value写入通道 channel - value // 2. 接收数据直接丢弃不保存 -channel // 3. 接收数据赋值给变量x x : -channel // 4. 接收判断通道状态重点 // oktrue通道未关闭正常读到数据 // okfalse通道已关闭无数据可读 x, ok : -channel阻塞规则通道无数据可读时接收操作阻塞无缓冲通道、缓冲区已满的有缓冲通道发送操作阻塞。四、无缓冲 Channel同步通道定义创建时容量为 0缓冲区无法存储任何数据。核心特性发送和接收必须同时就绪否则阻塞先执行发送ch - val阻塞等待其他协程接收先执行接收-ch阻塞等待其他协程发送只有收发双方同时到位数据才会完成传递协程继续向下执行。示例 1利用无缓冲通道实现协程同步gopackage main import fmt // 全局通道 var ch make(chan int) func Printer(str string) { for _, v : range str { fmt.Print(string(v)) } fmt.Println() } func person1() { Printer(hello) ch - 666 // 发送数据通知person1执行完毕 } func person2() { -ch // 阻塞等待收到数据后才执行打印 Printer(world) } func main() { go person1() go person2() for {} // 阻塞主协程防止程序退出 }执行顺序固定hello先打印再打印world实现协程同步。示例 2无缓冲通道完整读写演示gopackage main import ( fmt time ) func main() { // 无缓冲通道 ch : make(chan int, 0) fmt.Printf(len(ch)%d, cap(ch)%d\n, len(ch), cap(ch)) // 子协程发送数据 go func() { for i : 0; i 3; i { fmt.Println(子协程 i , i) ch - i // 无缓冲会阻塞等待主协程读取 } }() time.Sleep(2 * time.Second) // 主协程循环读取 for i : 0; i 3; i { num : -ch fmt.Println(num , num) } }五、有缓冲 Channel异步通道定义创建时指定容量 capacity内部自带缓冲区可以预先存储多条数据。go// 缓冲区最多存3个int ch : make(chan int, 3)核心特性缓冲区未满发送数据不会阻塞直接存入缓冲区缓冲区已满发送操作阻塞等待其他协程读取腾出空间缓冲区有数据接收操作直接读取不阻塞缓冲区为空接收操作阻塞。示例有缓冲通道读写gopackage main import ( fmt time ) func main() { ch : make(chan int, 3) go func() { for i : 0; i 3; i { ch - i fmt.Printf(子协程[%d] len(ch)%d, cap(ch)3\n, i, len(ch)) } }() time.Sleep(2 * time.Second) for i : 0; i 3; i { num : -ch fmt.Println(num , num) } }六、关闭 Channel close ()使用场景确认后续不会再向通道发送数据时调用close(ch)关闭通道。重要规则关闭后的通道禁止发送数据发送会直接 panic 崩溃通道关闭后缓冲区剩余数据仍可正常读取缓冲区数据全部读完后再次读取会返回okfalse。方式 1if 判断 ok 读取通道gopackage main import fmt func main() { ch : make(chan int) go func() { for i : 0; i 5; i { ch - i } close(ch) // 写完数据关闭通道 }() for { num, ok : -ch if ok true { fmt.Println(num , num) } else { fmt.Println(通道已关闭退出循环) break } } }方式 2range 遍历通道推荐for range会自动循环读取通道数据通道关闭后自动结束循环无需手动判断 okgofor num : range ch { fmt.Println(num , num) }七、单向 Channel只读 / 只写通道语法区分箭头方向go// 双向通道可读可写默认 var ch chan int // 只写通道chan-只能发送数据不能读取 var writeCh chan- float64 // 只读通道-chan只能读取数据不能发送 var readCh -chan int转换规则双向通道可以隐式转为单向通道单向通道不能转回双向通道编译报错。gopackage main import fmt func main() { ch : make(chan int) // 双向通道 // 双向转单向合法 var writeCh chan- int ch var readCh -chan int ch writeCh - 666 // 只写通道执行发送 -readCh // 只读通道执行接收 // 错误单向无法转回双向 // var ch2 chan int writeCh }实战场景生产者消费者模型单向通道用于函数参数约束规范函数行为代码可读性更高producer 生产者只写通道负责生产数据写入管道consumer 消费者只读通道负责读取管道数据gopackage 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() { ch : make(chan int) go producer(ch) // 开启生产者协程 consumer(ch) // 主线程消费 }八、Channel 核心复习总结Go 并发核心思想通信共享内存channel 是 goroutine 通信载体channel 是引用类型分无缓冲、有缓冲两类无缓冲通道收发必须成对出现天然实现同步有缓冲通道异步存储数据close()关闭通道后不能发送但剩余数据可读取range 简化通道循环读取单向通道约束读写权限常用于生产者消费者模型阻塞逻辑无数据读阻塞、缓冲区满写阻塞。