Golang语言之channel快速入门篇

news/2025/3/17 6:35:05/文章来源:https://www.cnblogs.com/yinzhengjie/p/18342510

                                              作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

目录
  • 五.channel
    • 1.channel概述
    • 2.管道入门案例
      • 2.1 有缓冲管道和无缓冲管道概述
      • 2.2 有缓冲管道
      • 2.3 无缓冲管道
    • 3.管道的关闭
      • 3.1 管道关闭操作结果概述
      • 3.2 管道关闭案例
      • 3.3 判断通道是否关闭
    • 4.管道的遍历
    • 5.协程和管道协同工作案例
    • 6.声明只读只写的管道
      • 6.1 单向通道概述
      • 6.2 声明单向管道
      • 6.3 单向管道作为参数传递
    • 7.管道的阻塞
      • 7.1 什么时候会出现管道的阻塞
      • 7.2 只写不读缓冲管道满就会阻塞
      • 7.3 只读不写的情况下会阻塞管道
  • 六.select多路复用
    • 1.select语句概述
    • 2.select案例

五.channel

1.channel概述

image-20240730230703414

共享内存交换数据弊端:- 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。- 虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。- 为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。Go语言采用的并发模型是CSP(Communicating Sequential Processes),提倡通过"通信共享内存"而不是通过"共享内存实现通信"。管道(channel)特质介绍:- 管道(channel)是一种特殊的类型,本质就是一个类似"队列"的数据结构;- 管道(channel)像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序;- 管道(channel)自身是线程安全,多协程访问时,不需要加锁;- 管道(channel)是有类型的,一个string的管道只能存放string类型数据;- 管道(channel)是可以让一个goroutine发送特定值到另一个goroutine的通信机制;channel是Go语言中一种特有的类型。声明通道类型变量的格式如下:var 变量名称 chan 元素类型其中:chan:是关键字元素类型:是指通道中传递元素的类型举几个例子:var ch1 chan int   // 声明一个传递整型的通道var ch2 chan bool  // 声明一个传递布尔型的通道var ch3 chan []int // 声明一个传递int切片的通道

2.管道入门案例

2.1 有缓冲管道和无缓冲管道概述

声明的通道类型变量需要使用内置的make函数初始化之后才能使用。具体格式如下:make(chan 元素类型, [缓冲大小])需要传递两个参数:- 第一个参数是channel存储的数据类型,比如"chan int"表示存储的是int类型。- 第二个参数:指的是channel的容量大小,channel的缓冲大小是可选的。若不指定默认值为0。若容量为0则无法从中写入数据,如果非要写会报错"fatal error: all goroutines are asleep - deadlock!"有缓冲管道特点:- 1.只要管道的容量大于零,那么该管道就属于有缓冲的管道,管道的容量表示管道中最大能存放的元素数量。- 2.当管道内已有元素数达到最大容量后,再向管道执行发送操作就会阻塞,除非有从管道执行接收操作。无缓冲管道特点:- 1.无缓冲的管道又称为阻塞的管道,单来说就是无缓冲的管道必须有至少一个接收方才能发送成功。- 2.无缓冲的管道只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段。- 3.同理,如果对一个无缓冲管道执行接收操作时,没有任何向管道中发送值的操作那么也会导致接收操作阻塞。- 4.使用无缓冲管道进行通信将导致发送和接收的goroutine同步化,因此,无缓冲管道也被称为同步管道。

2.2 有缓冲管道

package mainimport ("fmt"
)func main() {// 1.定义一个int类型的管道var intChan chan int// 未初始化的通道类型变量其默认零值是nil。fmt.Printf("intChan = %v\n", intChan)//2.通过make初始化有缓冲管道,管道可以存放3个int类型的数据 intChan = make(chan int, 3)// 3.管道是引用类型fmt.Printf("intChan的值: %v\n", intChan)// 4.向管道存放数据intChan <- 100intChan <- 200// 存储的数据不能大于管道channel容量。// intChan <- 300// intChan <- 400// 5.查看管道的长度fmt.Printf("intChan的实际大小: %d, 容量是: %d\n", len(intChan), cap(intChan))// 6.在管道中读取数据data01 := <-intChandata02 := <-intChan// 注意,在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错"fatal error: all goroutines are asleep - deadlock!"。// data03 := <-intChanfmt.Printf("data = %d\n", data01)fmt.Printf("intChan的实际大小: %d, 容量是: %d\n", len(intChan), cap(intChan))fmt.Printf("data = %d\n", data02)// fmt.Printf("data = %d\n", data03)
}

2.3 无缓冲管道

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc Consumer(ch chan bool) {// 在没有拿到数据之前,下面这行代码会一直处于阻塞状态哟!data := <-chfmt.Println("in Consumer ... ", data)wg.Done()
}func main() {// 定义无缓冲管道ch := make(chan bool)wg.Add(1)go Consumer(ch)fmt.Printf("已经开启Consumer协程...ch的容量为:%d,长度为:%d\n", cap(ch), len(ch))// 故意阻塞住主线程3秒。time.Sleep(time.Second * 3)// 阻塞一段时间后,主线程再尝试发送数据,只要主线程发送数据,Consumer协程就可以不阻塞啦~ch <- truefmt.Printf("main函数执行结束...ch的容量为:%d,长度为:%d\n", cap(ch), len(ch))wg.Wait()
}

3.管道的关闭

3.1 管道关闭操作结果概述

img

上面的表格中总结了对不同状态下的通道执行相应操作的结果。一个通道值是可以被垃圾回收掉的。通道通常由发送方执行关闭操作,并且只有在接收方明确等待通道关闭的信号时才需要执行关闭操作。它和关闭文件不一样,通常在结束操作之后关闭文件是必须要做的,但关闭通道不是必须的。关闭后的通道有以下特点:- 对一个关闭的通道再发送值就会导致 panic。- 对一个关闭的通道进行接收会一直获取值直到通道为空。- 对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。- 关闭一个已经关闭的通道会导致panic。

3.2 管道关闭案例

package mainimport "fmt"func main() {var strChan chan stringstrChan = make(chan string, 5)strChan <- "JasonYin"strChan <- "https://www.cnblogs.com/yinzhengjie"// 我们通过调用内置的close函数来关闭通道。close(strChan)// 管道不能重复关闭,否则会报错"panic: close of closed channel"// close(strChan)// 关闭管道后就不能写入数据了,会报错"panic: send on closed channel"// strChan <- "尹正杰"// 管道关闭后,是可以读取数据的data := <-strChanfmt.Printf("data = %v\n", data)}

3.3 判断通道是否关闭

package mainimport ("fmt"
)func Consumer(ch chan string) {for {/*对一个通道执行接收操作时支持使用如下多返回值模式。- value:从通道中取出的值,如果通道被关闭则返回对应类型的零值。- ok:通道ch关闭时返回 false,否则返回true。*/value, ok := <-chif !ok {fmt.Println("通道已关闭")break}fmt.Printf("value: %#v ok: %#v\n", value, ok)}
}func main() {ch := make(chan string, 2)ch <- "尹正杰"ch <- "https://www.cnblogs.com/yinzhengjie"close(ch)Consumer(ch)
}

4.管道的遍历

package mainimport "fmt"func listChannel(ch chan bool) {/*管道的遍历:管道支持for-range的方式进行遍历。管道遍历注意三个细节:- 1.如果管道没有关闭,则会出现deadlock的错误;- 2.如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历;- 3.目前Go语言中并没有提供一个不对通道进行读取操作就能判断通道是否被关闭的方法,不能简单的通过len(ch)操作来判断通道是否被关闭;*/for value := range ch {fmt.Printf("value = %t\n", value)}
}func main() {var boolChan chan boolboolChan = make(chan bool, 6)for i := 0; i < 2; i++ {boolChan <- trueboolChan <- falseboolChan <- true}// 关闭管道,避免在遍历时出错close(boolChan)listChannel(boolChan)}

5.协程和管道协同工作案例

image-20240731001706166

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroup// Producer 生产者产生数据
func Producer(strChan chan string) {defer wg.Done()for i := 1; i <= 20; i++ {data := fmt.Sprintf("饺子id = %d", i)strChan <- datafmt.Printf("Duang~新包的%s\n", data)time.Sleep(time.Second)}// 管道关闭,避免遍历时报错close(strChan)}// Consumer 消费者消费数据
func Consumer(strChan chan string) {defer wg.Done()// 遍历数据for value := range strChan {fmt.Printf("吧唧,吃了%s\n", value)}}func main() {strChan := make(chan string, 20)wg.Add(2)// 开启读和写的协程go Producer(strChan)go Consumer(strChan)wg.Wait()fmt.Println("吃饺子程序运行结束...")}

6.声明只读只写的管道

6.1 单向通道概述

在某些场景下我们可能会将管道作为参数在多个任务函数间进行传递。我们会选择在不同的任务函数中对管道的使用进行限制,比如限制管道在某个函数中只能执行发送或只能执行接收操作。

6.2 声明单向管道

package mainimport ("fmt"
)func main() {// 1.默认情况下,管道是双向的,即可读可写var intChan01 chan intintChan01 = make(chan int, 3) // 初始化数据intChan01 <- 100              // 写入数据到管道data01 := <-intChan01         // 从管道中读取数据fmt.Printf("data01 = %d\n", data01)// 2.声明单向的只写管道,即只能写不能读var intChan02 chan<- intintChan02 = make(chan int, 3) // 初始化数据intChan02 <- 200              // 写入数据到管道// data02 := <-intChan02         // 无法从管道中读取数据,会报错"invalid operation: cannot receive from send-only channel intChan02 (variable of type chan<- int)"// fmt.Printf("data02 = %d\n", data02)// 3.声明单向的只读管道,即只能读不能写var intChan03 <-chan int// intChan03 <- 200 // 无法写入数据到管道,会报错"invalid operation: cannot send to receive-only channel intChan03 (variable of type <-chan int)"if intChan03 != nil {data03 := <-intChan03fmt.Printf("data03 = %d\n", data03)}
}

6.3 单向管道作为参数传递

package mainimport ("fmt"
)// Producer 返回一个只写的管道,并持续将符合条件的数据发送至返回的管道中,数据发送完成后会将返回的管道关闭
func Producer() <-chan int {ch := make(chan int, 2)// 创建一个新的goroutine执行发送数据的任务go func() {// 将10以内的偶数返回for i := 0; i <= 10; i++ {if i%2 == 0 {ch <- i}}// 任务完成后关闭通道,避免另外的协程在遍历时出错。close(ch)}()return ch
}// Consumer参数为只读管道
func Consumer(ch <-chan int) int {sum := 0for value := range ch {fmt.Printf("in Consumer ... %d + %d \n", sum, value)sum += value}return sum
}func main() {channel := Producer()result := Consumer(channel)fmt.Printf("in main ... \tresult = %d\n", result)
}

7.管道的阻塞

7.1 什么时候会出现管道的阻塞

- 1.只读不写的情况下会阻塞管道- 2.只读不写的情况下会阻塞管道

7.2 只写不读缓冲管道满就会阻塞

image-20240731215116486

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroup// Producer 生产者产生数据
func Producer(strChan chan string) {defer wg.Done()for i := 1; i <= 20; i++ {data := fmt.Sprintf("饺子id = %d", i)strChan <- datafmt.Printf("Duang~新包的%s\n", data)time.Sleep(time.Second)}// 管道关闭,避免遍历时报错close(strChan)}// Consumer 消费者消费数据
func Consumer(strChan chan string) {defer wg.Done()// 遍历数据for value := range strChan {fmt.Printf("吧唧,吃了%s\n", value)}}func main() {// 缓冲管道大小为5strChan := make(chan string, 5)wg.Add(1)// 只写不读缓冲管道满就会阻塞,报错: "fatal error: all goroutines are asleep - deadlock!"go Producer(strChan)// go Consumer(strChan)wg.Wait()fmt.Println("吃饺子程序运行结束...")}

7.3 只读不写的情况下会阻塞管道

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroup// Producer 生产者产生数据
func Producer(strChan chan string) {defer wg.Done()for i := 1; i <= 20; i++ {data := fmt.Sprintf("饺子id = %d", i)strChan <- datafmt.Printf("Duang~新包的%s\n", data)time.Sleep(time.Second)}// 管道关闭,避免遍历时报错close(strChan)}// Consumer 消费者消费数据
func Consumer(strChan chan string) {defer wg.Done()// 遍历数据for value := range strChan {fmt.Printf("吧唧,吃了%s\n", value)}}func main() {// 缓冲管道大小为5strChan := make(chan string, 5)wg.Add(1)// 只读不写缓冲管道就会阻塞,报错: "fatal error: all goroutines are asleep - deadlock!"// go Producer(strChan)go Consumer(strChan)wg.Wait()fmt.Println("吃饺子程序运行结束...")}

六.select多路复用

1.select语句概述

在某些场景下我们可能需要同时从多个管道接收数据。 Go语言内置了select关键字,解决多个管道的选择问题,也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行。select语句具有以下特点:- 1.case后面必须进行IO操作,不能是等值,随机去选择一个IO操作;- 2.default防止select被阻塞住,如果没有case符合,则走default分支;

2.select案例

package mainimport ("fmt""time"
)func ProducerInt(ch chan int) {ch <- 10for i := 100; i <= 110; i++ {time.Sleep(time.Second * 3)ch <- i}
}func ProducerStr(ch chan string) {for i := 0; i < 10; i++ {time.Sleep(time.Second * 2)ch <- fmt.Sprintf("golang00%d\n", i)}
}func main() {// 定义一个int管道intChan := make(chan int, 1)// 定义一个string管道strChan := make(chan string, 1)go ProducerInt(intChan)go ProducerStr(strChan)for count := 1; count <= 60; count++ {// select和前面学习的switch语句运行逻辑很相似select {// case后面必须进行IO操作,不能是等值,随机去选择一个IO操作,多个case如果都符合,则会随机选择一个去执行。case value := <-intChan:fmt.Printf("intChan = %v\n", value)case value := <-intChan:fmt.Printf("strChan = %v\n", value)// 如果所有的case语句都不符合,则走默认的default语句哟~default:fmt.Printf("程序已经运行%d秒\n", count)time.Sleep(time.Second * 1)}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/777979.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《机器人SLAM导航核心技术与实战》第1季:第8章_激光SLAM系统

《机器人SLAM导航核心技术与实战》第1季:第8章_激光SLAM系统 视频讲解【第1季】8.第8章_激光SLAM系统-视频讲解【第1季】8.1.第8章_激光SLAM系统_Gmapping算法-视频讲解【第1季】8.2.第8章_激光SLAM系统_Cartographer算法-视频讲解【第1季】8.3.第8章_激光SLAM系统_LOAM算法-视…

Sharding-JDBC 几行配置实现读写分离~

Sharding-JDBC 几行配置实现读写分离~ 2022-05-252,435阅读6分钟 大家好,我是不才陈某~ 今天聊一下如何通过Sharding-JDBC简单的实现读写分离~ 为什么要读写分离? 读写分离则是将事务性的增、改、删操作在主库执行,查询操作在从库执行。 一般业务的写操作都是比较耗时,为了…

redis为什么用单线程不用多线程

1.线程上下文切换开销。 2.线程之间共享变量,加锁解锁开销。瓶颈不在cpu而是在内存和网络io带宽。 3.多线程代码复杂。

全网最适合入门的面向对象编程教程:32 Python 的内置数据类型-类 Class 和实例 Instance

在Python中,类(Class)是创建对象(实例,Instance)的模板。类定义了对象的属性和行为,而实例是类的具体对象,具有独立的属性值。全网最适合入门的面向对象编程教程:32 Python 的内置数据类型-类 Class 和实例 Instance摘要: 在Python中,类(Class)是创建对象(实例,…

小猪佩奇学英语——第二天

第二天 例句被动语态:be+动词的过去分词Mr.Dinosaur is lost My boot is lost My keys are lost描述某个人最喜欢的东西是什么时,要用所有格(某个人的),因为某个人和东西是两个独立个体如:he 和 movie是两个独立个体,不能连起来,要用His Georges favourite toy is Mr.D…

epoll的解释

同步阻塞最差。 同步非阻塞可能会跑一圈一个作业也收不上来。 select / poll 可以确定有人完成作业了,因为是有人完成作业后举手,你才下去收作业的。但是需要一个一个去问。 epoll就很好了。

客户端和服务器通过http协议基于》》tcp协议,经过三次握手进行socket连接

io多路复用在两个地方被用到:一个是网络通信。 一个是redis的线程模型。

Git 开发规范

目录Git 开发规范分支管理策略git flowgithub flowgitlab flowtrunk-based development总结Commit Message Git 开发规范 分支管理策略 git flowVincent Driessen 于2010年提出的分支模型,可以说是最早、最全面的分支管理策略了,后续出现的分支管理策略基本都是基于 git flow…

k8s工作负载控制器--DaemonSet

目录一、概述二、适用场景三、基本操作1、官网的DaemonSet资源清单2、字段解释3、编写DaemonSet资源清单4、基于yaml创建DaemonSet5、注意点5.1、必须字段5.2、DaemonSet 对象的名称5.3、.spec.selector 与 .spec.template.metadata.labels之间的关系6、查看DaemonSet6.1、查看…

【考研概率论】两个集合的交集与并集相等意味着什么?

今天要给大家分享的笔记是:《交集和并集相等的两个事件一定是相同的事件》:

实现一个终端文本编辑器来学习golang语言:第三章文本查看器part1

本章我们来完成文本编辑器的文件打开和查看功能,最后成品如上图。我们将分4步,逐渐完成本章所需功能。内容比较多,会分为两个部分,第一部分主要关注于“View视图”和“buffer及文本读取”。 如上图最终效果所示,我们希望在终端的最下方增加一个状态栏,能够展示当前被打开…

基于和声搜索算法(Harmony Search,HS)的机器设备工作最优调度方案求解matlab仿真

1.程序功能描述通过和声搜索算法(Harmony Search,HS)实现机器设备工作时间调度,使得多个机器进行并行工作,使得最终完成任务的时间达到最小。仿真结果输出工作调度甘特图以及和声搜索算法的适应度值收敛曲线。2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序…