当前位置: 首页 > news >正文

golang学习笔记26-管道(Channel)【重要】

本节也是GO核心部分,很重要。
注意:Channel更准确的翻译应该是通道,管道实际上叫Pipeline。当然,在GO中,管道专指Channel。
管道本质上是一个队列,队列是数据结构的内容,这里不做赘述。管道对协程的主要作用是提供安全性:因其先进先出的特性,保证了多个协程操作同一个管道时,不会发生资源抢夺问题。
管道的语法是:var 变量名 chan 管道存放的数据类型。管道是引用类型,且和map一样,必须初始化才能写入数据,即make后才能使用。

目录

    • 一、读写数据
    • 二、管道的关闭
    • 三、管道的遍历
    • 四、协程和管道协同工作
    • 五、管道的声明
    • 六、select

一、读写数据

管道用<-取(读)数据,存(写)数据,注意,这里的”读“是取出数据,”写“是存入数据,这都会导致管道长度(不是容量)改变!在没有使用协程的情况下,若没有定义管道长度(定义了管道长度的叫缓冲管道),即空管道,这时就取数据,或满管道时放数据,则go都会报错:fatal error: all goroutines are asleep - deadlock!。这里提到了死锁,也是操作系统的概念。
例:

package mainimport ("fmt"
)func main() {// 定义一个容量为3的管道作为缓冲,避免阻塞ch := make(chan int, 3)// 存入数据ch <- 1ch <- 2ch <- 3fmt.Printf("存入数据后:长度 = %d, 容量 = %d\n", len(ch), cap(ch))// 再次存入数据,由于管道已满,这一行会阻塞程序,除非有数据被取出// ch <- 4 // 取消注释这一行将会导致阻塞,go会报错// 取出数据fmt.Printf("取出数据:%d\n", <-ch)fmt.Printf("取出数据:%d\n", <-ch)fmt.Printf("取出数据:%d\n", <-ch)fmt.Printf("取出数据后:长度 = %d, 容量 = %d\n", len(ch), cap(ch))// 尝试再取数据,管道已空,这会引发阻塞// 如果取消注释下一行,程序将会在此处阻塞,go会报错// fmt.Printf("尝试取出额外的数据:%d\n", <-ch)fmt.Println("程序结束")
}

二、管道的关闭

管道关闭后,就不能向它写数据了,但可以读数据。例:

package mainimport ("fmt"
)func main() {// 创建一个容量为3的缓冲管道ch := make(chan int, 3)// 向管道中写入数据ch <- 10ch <- 20ch <- 30// 关闭管道close(ch)// 尝试再次写入数据会导致运行时错误:panic: send on closed channel// ch <- 40 // 取消注释会导致panic,因为管道已关闭// 读数据,关闭的管道仍然可以读取剩余的数据fmt.Println("从管道读取数据:", <-ch) // 输出 10fmt.Println("从管道读取数据:", <-ch) // 输出 20fmt.Println("从管道读取数据:", <-ch) // 输出 30// 继续读取,管道已空,读取到的是零值fmt.Println("尝试读取空管道的数据:", <-ch) // 输出 0,读取的是通道类型的零值fmt.Println("程序结束")
}

三、管道的遍历

管道由于本质是队列,所以只支持for-range的方式进行遍历,请注意两个细节:
1)对于管道的for-range,只返回value,不返回index
2)在遍历时,如果管道没有关闭,则会出现死锁(deadlock)的错误
3)在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

package mainimport "fmt"func main() {ch := make(chan int, 5)// 向管道中写入数据for i := 1; i <= 3; i++ {ch <- i}// 1. 如果管道未关闭,会导致 deadlock 错误// fmt.Println("未关闭管道时遍历:")// for v := range ch {//     fmt.Println(v)// }// 2. 如果管道关闭,遍历会正常结束close(ch)fmt.Println("关闭管道后遍历:")for v := range ch {fmt.Println(v)}// 若管道关闭后,再次写入数据会报错// ch <- 4  // 这里会引发 panic: send on closed channel
}

四、协程和管道协同工作

请完成协程和管道协同工作的案例,具体要求:
1)开启一个writeDatat协程,向管道中写入50个整数.
2)开启一个readData协程,从管道中读取writeData写入的数据。
3)注意:writeData和readDate操作的是同一个管道
4)主线程需要等待writeData和readDate协程都完成工作才能退出

package mainimport ("fmt""sync"
)// 向管道中写入数据的协程
func writeData(ch chan int, wg *sync.WaitGroup) {defer wg.Done() // 协程执行完毕时通知WaitGroupfor i := 1; i <= 50; i++ {ch <- ifmt.Printf("写入数据: %d\n", i)}close(ch) // 写入完成后关闭管道
}// 从管道中读取数据的协程
func readData(ch chan int, wg *sync.WaitGroup) {defer wg.Done() // 协程执行完毕时通知WaitGroupfor data := range ch {fmt.Printf("读取数据: %d\n", data)}
}func main() {// 创建一个大小为10的管道(缓冲区大小可以根据需求调整)ch := make(chan int, 10)// 创建WaitGroup来同步主线程和协程var wg sync.WaitGroup// 启动协程,并设置等待数量为2wg.Add(2)go writeData(ch, &wg)go readData(ch, &wg)// 等待所有协程完成wg.Wait()fmt.Println("所有数据写入和读取完成,程序退出")
}

五、管道的声明

默认情况下,管道是可读可写的,但可以声明为只读或只写。

package mainimport ("fmt"
)func main() {// 创建一个缓冲管道,避免阻塞dataChan := make(chan int, 5)// 声明只写管道var writeChan chan<- int = dataChan// 声明只读管道var readChan <-chan int = dataChan// 向只写管道写入数据for i := 1; i <= 5; i++ {writeChan <- ifmt.Printf("写入数据: %d\n", i)}close(writeChan) // 关闭写入管道// 从只读管道读取数据for value := range readChan {fmt.Printf("读取数据: %d\n", value)}
}

六、select

这个select可不是数据库语言,这是用于解决多个管道的选择问题的,select操作也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行。注意,这不是switch,switch是顺序选择,这里是随机选择。一些细节:
1.case后面必须进行的是io操作,即case c := <-chan1:,不能是等值,即case c:
2.default防止select被阻塞,加入default

package mainimport ("fmt""time"
)func main() {chan1 := make(chan int) // 有了select,即便无缓冲也不会阻塞chan2 := make(chan string)go func() {time.Sleep(time.Second * 1)chan1 <- 1}()go func() {time.Sleep(time.Second * 2)chan2 <- "hello"}()select {case v := <-chan1:fmt.Println("intchan:", v)case v := <-chan2:fmt.Println("stringchan:", v)default:fmt.Println("防止阻塞")}
}

上述代码其实不完善,因为无论select之前怎么改,程序都只输出”防止阻塞“,若要执行case,就需要for循环来持续监听管道

package mainimport ("fmt""time"
)func main() {// 创建一个缓冲通道chan1 := make(chan int, 1)chan2 := make(chan string, 1)// 启动 goroutine 向 chan1 写入数据go func() {time.Sleep(time.Second * 1)chan1 <- 1}()// 启动 goroutine 向 chan2 写入数据go func() {time.Sleep(time.Second * 2)chan2 <- "hello"}()// 持续监听通道for {select {case v := <-chan1:fmt.Println("intchan:", v) // 如果 chan1 被写入,打印数据return                     // 读取后退出循环case v := <-chan2:fmt.Println("stringchan:", v) // 如果 chan2 被写入,打印数据return                        // 读取后退出循环default:fmt.Println("防止阻塞") // 如果没有通道可读,打印该信息// 等待一段时间,防止立即进入下一循环而输出过多信息time.Sleep(500 * time.Millisecond)}}
}

多次运行你会发现,总是输出第一个协程的信息,但这不违背select随机选取的原则,因为select选取的仅是准备好的通道。由于第二个协程比第一个协程慢1秒,所以总是第一个先准备好。所以想要随机输出协程信息,睡眠时间都改为一样即可,比如1秒,读者可自行尝试,多次运行,结果一定会不同。

相关文章:

  • CSS中的font-variation-settings:探索字体的可变性
  • 鸿蒙开发(NEXT/API 12)【请求用户授权】手机侧应用开发
  • 计算机毕业设计 二手图书交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • SpringAOP实现的两种方式-JDK动态代理和CGLIB动态代理
  • 【TypeScript学习】TypeScript基础学习总结一
  • 数字教学时代:构建高效在线帮助中心的重要性
  • C嘎嘎入门篇:类和对象(2)
  • 基于JAVA Web的校园快递代领系统设计与实现(源码+定制+文档)
  • 基于单片机的温湿度检测判断系统
  • 无监督算法目标识别-工业异常检测模型Padim+PatchCore的C++_libtorch实现
  • 【Android】浅析六大设计原则
  • 拓扑结构的理解
  • NVIDIA G-Assist 项目:您的游戏和应用程序AI助手
  • 使用docker搭建zk集群
  • 【新闻转载】Storm-0501:勒索软件攻击扩展到混合云环境
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • angular2开源库收集
  • HTTP 简介
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Js基础知识(一) - 变量
  • learning koa2.x
  • Redis中的lru算法实现
  • VUE es6技巧写法(持续更新中~~~)
  • 使用 QuickBI 搭建酷炫可视化分析
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 突破自己的技术思维
  • 我感觉这是史上最牛的防sql注入方法类
  • 一天一个设计模式之JS实现——适配器模式
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • postgresql行列转换函数
  • 如何用纯 CSS 创作一个货车 loader
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​flutter 代码混淆
  • (7)摄像机和云台
  • (C)一些题4
  • (Forward) Music Player: From UI Proposal to Code
  • (八)Flask之app.route装饰器函数的参数
  • (八)Spring源码解析:Spring MVC
  • (二)学习JVM —— 垃圾回收机制
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (六)Hibernate的二级缓存
  • (三)Kafka离线安装 - ZooKeeper开机自启
  • (微服务实战)预付卡平台支付交易系统卡充值业务流程设计
  • (一)u-boot-nand.bin的下载
  • (一一四)第九章编程练习
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)Sublime Text3配置Lua运行环境
  • (转)四层和七层负载均衡的区别
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET delegate 委托 、 Event 事件
  • .NET Framework .NET Core与 .NET 的区别