Golang并发编程-协程goroutine的信道(channel)

news2025/3/15 7:34:12

文章目录

  • 前言
  • 一、信道的定义与使用
    • 信道的声明
    • 信道的使用
  • 二、信道的容量与长度
  • 三、缓冲信道与无缓冲信道
      • 缓冲信道
      • 无缓冲信道
  • 四、信道的初体验
    • 信道关闭的广播机制
  • 总结


前言

Goroutine的开发,当遇到生产者消费者场景的时候,离不开 channel(信道)的使用。
信道,就是一个管道,连接多个goroutine程序 ,它是一种队列式的数据结构,遵循先入先出的规则。
在这里插入图片描述


一、信道的定义与使用

信道的声明

信道声明的两种方式:

// 先声明再初始化
var 信道实例 chan 信道类型
信道实例 = make(chan 信道类型)

// 上面两句合并
信道实例 := make(chan 信道类型)

信道的使用

发送数据,接收数据

// 往信道中发送数据
pipline<- 200

// 从信道中取出数据,并赋值给mydata
mydata := <-pipline

信道用完了,可以对其进行关闭,避免有人一直在等待。但是你关闭信道后,接收方仍然可以从信道中取到数据,只是接收到的会永远是 0。

close(pipline)

当从信道中读取数据时,可以有多个返回值,其中第二个可以表示 信道是否被关闭,如果已经被关闭,ok 为 false,若还没被关闭,ok 为true。

x, ok := <-pipline

二、信道的容量与长度

一般创建信道都是使用 make 函数,make 函数接收两个参数

  • 第一个参数:必填,指定信道类型
  • 第二个参数:选填,不填默认为0,指定信道的容量(可缓存多少数据)

对于信道的容量,很重要,这里要多说几点:

  1. 当容量为0时,说明信道中不能存放数据,在发送数据时,必须要求立马有人接收,否则会报错。此时的信道称之为无缓冲信道。
  2. 当容量为1时,说明信道只能缓存一个数据,若信道中已有一个数据,此时再往里发送数据,会造成程序阻塞。 利用这点可以利用信道来做锁。
  3. 当容量大于1时,信道中可以存放多个数据,可以用于多个协程之间的通信管道,共享资源。

信道的容量,可以使用 cap 函数获取 ,而信道的长度,可以使用 len 长度获取。

package main

import "fmt"

func main() {
	pipline := make(chan int, 10)
	fmt.Printf("信道可缓冲 %d 个数据\n", cap(pipline))
	pipline <- 1
	pipline <- 1
	fmt.Printf("信道中当前有 %d 个数据\n", len(pipline))
}

输出结果

[root@work day01]# go run channel.go 
信道可缓冲 10 个数据
信道中当前有 2 个数据

三、缓冲信道与无缓冲信道

按照是否可缓冲数据可分为:缓冲信道无缓冲信道

缓冲信道

允许信道里存储一个或多个数据,这意味着,设置了缓冲区后,发送端和接收端可以处于异步的状态。

pipline := make(chan int, 10)

无缓冲信道

在信道里无法存储数据,这意味着,接收端必须先于发送端准备好,以确保你发送完数据后,有人立马接收数据,否则发送端就会造成阻塞,原因很简单,信道中无法存储数据。也就是说发送端和接收端是同步运行的。

pipline := make(chan int)

四、信道的初体验

信道的用途就是传递数据信息,下面以经典的生产者和消费者场景,实践下信道是使用。
下面的例子中,声明了一个容量为10的传递整型的信道。生产者生产的数据放入信道,消费者将从信道的数据打印。
这里,我们消费者有两种,其实是演示了信道遍历的两种常见的方式。
第一种是用for range方式
第二种是用for循环,断言判断信道是否关闭,关闭了就退出。

package main

import (
	"fmt"
	"sync"
)

// 生产者
func producer(mychan chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 1; i <= 10; i++ {
		mychan <- i
		fmt.Printf("produce: %v\n", i)
		fmt.Printf("pipeline Length: %v \n", len(mychan))
	}

	// 记得 close 信道
	// 不然主函数中遍历完并不会结束,而是会阻塞。
	close(mychan)
}

//消费者 Range方式
func consumer_range(name string, mychan chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	for k := range mychan {
		fmt.Printf("consumer_range %v: %v\n", name, k)
	}
}

//消费者 断言方式
func consumer_assert(name string, mychan chan int, wg *sync.WaitGroup) {
	defer wg.Done()
	for {
	    // if data, ok := <-mychan; ok  ok为bool值,true表示正常接受,false表示通道关闭。
		if data, ok := <-mychan; ok {
			fmt.Printf("consumer_assert %v: %v\n", name, data)
		} else {
			break
		}
	}
}

func main() {

	var wg sync.WaitGroup
	pipline := make(chan int, 10)
	wg.Add(3)
	go producer(pipline, &wg)
	go consumer_range("ID1", pipline, &wg)
	go consumer_assert("ID2", pipline, &wg)
	wg.Wait()

}

输出的结果:

[root@work day01]# go run channel3.go 
produce: 1
pipeline Length: 0 
produce: 2
pipeline Length: 1 
produce: 3
pipeline Length: 2 
produce: 4
pipeline Length: 3 
produce: 5
pipeline Length: 4 
produce: 6
pipeline Length: 5 
produce: 7
pipeline Length: 6 
produce: 8
pipeline Length: 7 
produce: 9
pipeline Length: 8 
produce: 10
pipeline Length: 9 
consumer_assert ID2: 1
consumer_assert ID2: 2
consumer_assert ID2: 3
consumer_assert ID2: 4
consumer_assert ID2: 5
consumer_assert ID2: 6
consumer_assert ID2: 7
consumer_assert ID2: 8
consumer_assert ID2: 9
consumer_assert ID2: 10

信道关闭的广播机制

上面的案例中,有一点需要注意,我们的生产者函数中,在数据生产结束后,调用了close(mychan)方法,关闭了信道。如果不关闭程序还能正常执行吗?我们手动修改下代码,将代码改造如下,再次执行。

	// 记得 close 信道
	// 不然主函数中遍历完并不会结束,而是会阻塞。
	// close(mychan)

执行结果如下:

[root@work day01]# go run channel3.go 
produce: 1
pipeline Length: 0 
produce: 2
pipeline Length: 0 
produce: 3
pipeline Length: 1 
produce: 4
pipeline Length: 2 
produce: 5
pipeline Length: 3 
produce: 6
pipeline Length: 4 
produce: 7
pipeline Length: 5 
produce: 8
pipeline Length: 6 
produce: 9
pipeline Length: 7 
produce: 10
pipeline Length: 8 
consumer_assert ID2: 1
consumer_assert ID2: 3
consumer_assert ID2: 4
consumer_assert ID2: 5
consumer_assert ID2: 6
consumer_assert ID2: 7
consumer_assert ID2: 8
consumer_assert ID2: 9
consumer_assert ID2: 10
consumer_range ID1: 2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000010060?)
        /usr/local/go/src/runtime/sema.go:62 +0x27
sync.(*WaitGroup).Wait(0x0?)
        /usr/local/go/src/sync/waitgroup.go:116 +0x4b
main.main()
        /data/go_projects/third_packages/goroutine/day01/channel3.go:50 +0x157

goroutine 7 [chan receive]:
main.consumer_range({0x49bc4f, 0x3}, 0x0?, 0x0?)
        /data/go_projects/third_packages/goroutine/day01/channel3.go:25 +0x11f
created by main.main
        /data/go_projects/third_packages/goroutine/day01/channel3.go:48 +0xf9

goroutine 8 [chan receive]:
main.consumer_assert({0x49bc52, 0x3}, 0x0?, 0x0?)
        /data/go_projects/third_packages/goroutine/day01/channel3.go:34 +0x11d
created by main.main
        /data/go_projects/third_packages/goroutine/day01/channel3.go:49 +0x14d
exit status 2

通过上面的结果,我们看到消费者在消费完信道中的消息后,就panic退出了。
当程序一直在等待从信道里读取数据,而此时并没有人会往信道中写入数据。此时程序就会陷入死循环,造成死锁。
这里在现实生活中也有类似的例子,生产者是网红,每天摆摊,大量消费者蹲守在网红摊点前打卡,突然一天网红不出摊了,也没在微信和社交媒体更新动态,那消费者一天一直苦等。正确的做法是网红在社交媒体上发布下今天不出摊的通知,避免粉丝等待。

if data, ok := <-mychan; ok  ok为bool值,true表示正常接受,false表示通道关闭。

同理,我们上面的案例一样,当生产者不在生产的时候,应该关闭信道。因为信道关闭是有关播机制的,所有的channel接收者都会在chennel关闭时,立刻从阻塞等待中返回ok值为false。


总结

  1. 关闭一个未初始化的 channel 会产生 panic
  2. 重复关闭同一个 channel 会产生 panic
  3. 向一个已关闭的 channel 发送消息会产生 panic
  4. 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已被读取,则会读取到该类型的零值
  5. 从已关闭的 channel 读取消息永远不会阻塞,并且会返回一个为 false 的值,用以判断该 channel 是否已关闭(x,ok := <- ch)
  6. 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
  7. 如果写端没有写数据,也没有关闭channel 。<-ch; 会阻塞

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之第97题交错字符串

题目&#xff1a; 题解&#xff1a; bool isInterleave(char* s1, char* s2, char* s3) {int n strlen(s1), m strlen(s2), t strlen(s3);int f[m 1];memset(f, 0, sizeof(f));if (n m ! t) {return false;}f[0] true;for (int i 0; i < n; i) {for (int j 0; j &l…

Java进阶学习笔记12——final、常量

final关键字&#xff1a; final是最终的意思。可以修饰类、方法、变量。 修饰类&#xff1a;该类就被称为最终类&#xff0c;特点是不能被继承了。 修饰方法&#xff1a;该方法是最终方法&#xff0c;特点是不能被重写了。 修饰变量&#xff1a;该变量只能被赋值一次。 有些…

mybatis-plus 优雅的写service接口中方法(3)

多表联查 上文讲过了自定义sql &#xff0c;和wrapper的使用&#xff0c;但是我们可以发现 我们查询的都是数据库中的一张表&#xff0c;那么怎么进行多表联查呢&#xff0c;当然也是用自定义sql来进行实现 比如说 查询 id 为 1 2 4 的用户 并且 地址在北京 的 用户名称 普…

告诉老板,AI大模型应该这样部署!

导语 随着大语言模型创新的快速步伐&#xff0c;企业正在积极探索用例并将其第一个生成式人工智能应用程序部署到生产中。 随着今年LLM或LLMOps的部署正式开始&#xff0c;企业根据自己的人才、工具和资本投资结合了四种类型的LLM部署方法。请记住&#xff0c;随着新的 LLM 优…

第199题|关于函数的周期性问题|函数强化训练(六)|武忠祥老师每日一题 5月24日

解题思路&#xff1a;解这道题我们要用到下面这个结论 f(x)连续&#xff0c;以T为周期时&#xff0c;原函数以T为周期的充分必要条件是&#xff1a; (A) sin x显然是以π为周期的&#xff0c;我们可以看到并不等于0,根据结论&#xff0c;A的原函数显然不是周期函数。 (B) 的…

Linux|如何在 awk 中使用流控制语句

引言 当您从 Awk 系列一开始回顾我们迄今为止介绍的所有 Awk 示例时&#xff0c;您会注意到各个示例中的所有命令都是按顺序执行的&#xff0c;即一个接一个。但在某些情况下&#xff0c;我们可能希望根据某些条件运行一些文本过滤操作&#xff0c;这就是流程控制语句的方法。 …

Windows VS2022 C语言使用 sqlite3.dll 访问 SQLite数据库

今天接到一个学生C语言访问SQLite数据库的的需求: 第一步,SQLite Download Page下载 sqlite3.dll 库 下载解压,发现只有两个文件: 于是使用x64 Native Tools Command Prompt 终端 生成 sqlite3.lib 和 sqlite3.exp文件 LIB -def:sqlite3.def -out:sqlite3.lib -machin…

Cloneable接口和深拷贝

在java中如何对对象进行拷贝呢&#xff1f;我们可以使用Object类中的clone方法。 一、浅拷贝 在使用clone方法对对象进行拷贝的时候&#xff0c;需要注意&#xff1a; 1.需要重写clone方法&#xff1b; 2.clone方法的返回值是Object类&#xff0c;需要强制类型转化&#xf…

微信小程序-常用的视图容器类组件

一.组件分类 小程序中的组件也是由宿主环境提供的&#xff0c;开发者可以基于组件快速搭建出漂亮的页面结构。 官方把小程序的组件分为了9大类: (1) 视图容器 (2) 基础内容 (3) 表单组件 (4)导航组件 (5) 媒体组件 (6) map 地图组件 (7) canvas 画布组件 (8) 开放能力 (9) 无…

spark学习

standalone环境部署 1.standalone架构 standalone是完整的spark运行环境&#xff0c;其中&#xff1a;Master角色以Master进程存在&#xff0c;Worker角色以Woker进程存在&#xff0c;Driver角色在运行时存在Master进程内&#xff0c;Executeor运行在Worker进程内 standalon…

前缀和,差分算法理解

前缀和是什么&#xff1a; 前缀和指一个数组的某下标之前的所有数组元素的和&#xff08;包含其自身&#xff09;。前缀和分为一维前缀和&#xff0c;以及二维前缀和。前缀和是一种重要的预处理&#xff0c;能够降低算法的时间复杂度 说个人话就是比如有一个数组&#xff1a; …

【408真题】2009-13

“接”是针对题目进行必要的分析&#xff0c;比较简略&#xff1b; “化”是对题目中所涉及到的知识点进行详细解释&#xff1b; “发”是对此题型的解题套路总结&#xff0c;并结合历年真题或者典型例题进行运用。 涉及到的知识全部来源于王道各科教材&#xff08;2025版&…

【NumPy】NumPy与Pandas集成应用:数据处理的强强联合

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

从git上拉取项目进行操作

1.Git的概念 Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。它实现多人协作的机制是利用clone命令将项目从远程库拉取到本地库&#xff0c;做完相应的操作后再利用push命令从本地库将项目提交至远程库。 2.Git的工作流程…

mysql 删除特殊字符 表中存了特殊字符 换行符 回车符 word字符 查询不到

省流&#xff1a; UPDATE t1 SET f1 REPLACE(REPLACE( f1 , CHAR(10), ), CHAR(13), ); 用 replace() 函数将 换行符char(10) 和 回车符char(13) 替换为空字符串。 char(10)&#xff1a;换行 char(13)&#xff1a;回车 发现表里存进很多换行符&#xff0c;如下图&#xff1a…

【知识拓展】ngrok-高性价比的内网穿透工具

前言 使用google colab运行的web应用无法打开进行测试。 第一时间想到是否有相关工具能将内网映射到外网供访问。于是找到了ngrok。 ngrok 是什么&#xff0c;我们为什么要使用它&#xff1f; ngrok官网是一个全球分布的反向代理&#xff0c;无论您在哪里运行&#xff0c;它…

vue3 <script setup> 语法糖时间组件

<template><div><p>当前时间Current Time: {{ currentTime }}</p></div> </template><script setup> import { ref, onBeforeUnmount, onMounted } from vueconst currentTime ref()let interval // 声明 interval 变量const getTo…

二叉树OJ题目

一.二叉树第k层结点个数 有这样的一个思路&#xff1a;我既然要求第k层的结点个数&#xff0c;我肯定是要用到递归&#xff0c;那么当我在递归到第k层的时候我就开始判断&#xff0c;这一层是不是我所需要的那一层&#xff0c;如果是&#xff0c;就计数有几个节点&#xff0c;…

IDEA中好用的插件

IDEA中好用的插件 CodeGeeXMybatis Smart Code Help ProAlibaba Java Coding Guidelines​(XenoAmess TPM)​通义灵码常用操作 TranslationStatisticGrep Console CodeGeeX 官网地址&#xff1a;https://codegeex.cn/ 使用手册&#xff1a;https://zhipu-ai.feishu.cn/wiki/Cu…

cocosCreator动态生成二维码

cocosCreator 版本&#xff1a;3.7.2 开发语言&#xff1a;typeScript 我们在游戏开发中&#xff0c;经常会生成一个专属于玩家个人的二维码&#xff0c;比如说推广、充值等功能。 接到这个任务&#xff0c;在网上找了下&#xff0c;还是有很多教程的。但是这些教程大部分都是用…