go语言channel(管道)和 select的结合使用

news2024/11/28 14:38:56

给个小建议:如果是初学者,建议把基础知识朗读一遍,有个大概印象,后面思考多了,就会“由量变达到质变”,从而有所顿悟。

目录

  • 一、基础知识
  • 二、例子1
    • 1、管道ch的缓冲区为10,select中有case读取管道的数据
      • 代码示例
      • 输出结果
    • 2、管道ch的缓冲区为10,~~select中有case读取管道的数据~~
      • 代码示例
      • 输出结果
    • 3、~~管道ch的缓冲区为10~~ ,select中有case读取管道的数据
      • 代码示例
      • 输出结果
    • 4、~~管道ch的缓冲区为10,select中有case读取管道的数据~~
      • 代码示例
      • 输出结果
    • 5、补充知识:channel底层的等待队列
      • (1)协程阻塞,加入等待队列
        • 举例:下面展示了一个没有缓冲区的管道,有几个协程阻塞等待读数据。
      • (2)协程被唤醒:
      • (3)同一个协程里面,管道无缓冲区,会死锁
        • 代码示例
        • 输出结果
        • 底层示意图
      • (4)同一个协程里面,管道有缓冲区,不会死锁
        • 代码示例
        • 输出结果
        • 底层示意图
      • 注意:
    • 6、补充知识:select
    • 7、总结
  • 三、例子2
    • 代码示例
    • 输出结果
    • select 语句的执行顺序
  • 四、参考资料

一、基础知识

  1. 在Go语言中,管道是协程间通信的方式
  2. 管道是nil,则读写都会永久阻塞
  3. 管道关闭(通过close()函数关闭),则读 ;写 ,会触发panic
  4. 数据读写

    在这里插入图片描述
  5. 创建管道的两种方式:
    // 声明管道,值为nil
    var ch chan int 
    
    // make创建无缓冲管道和带缓冲管道
    ch1 := make(chan string)
    ch2 := make(chan string, 10)
    
  6. 管道是双向可用的,在函数间传递时,可以通过操作符(<-)来控制管道的可读和可写。
    func ChanParamRW(ch chan int) {
    	// 管道可读写
    }
    
    func ChanParamR(ch <- chan int) {
    	// 只能从管道读取数据
    }
    
    func ChanParaW(ch chan<- int) {
    	// 只能向管道写入数据
    }
    

二、例子1

1、管道ch的缓冲区为10,select中有case读取管道的数据

代码示例

func main() {
	ch := make(chan int, 10)
	go func() {
		for i := 0; i < 20; i++ {
			select {
			case ch <- 1:
				fmt.Println("输入1")
			case ch <- 2:
				fmt.Println("输入2")
			case <-ch:
				fmt.Println("输出")
			default:
				fmt.Println("default")
			}
		}

	}()

	time.Sleep(time.Millisecond)
}

输出结果

输入2
输入2
输入2
输入1
输入2
输入2
输出
输入2
输入2
输入2
输入1
输入1
输出
输入1
输出
输入2
输出
输出
输出
输入1

2、管道ch的缓冲区为10,select中有case读取管道的数据

代码示例

func main() {
	ch := make(chan int, 10)
	go func() {
		for i := 0; i < 20; i++ {
			select {
			case ch <- 1:
				fmt.Println("输入1")
			case ch <- 2:
				fmt.Println("输入2")			
			default:
				fmt.Println("default")
			}
		}

	}()

	time.Sleep(time.Millisecond)
}

输出结果

输入2
输入1
输入1
输入1
输入2
输入2
输入2
输入2
输入1
输入1
default
default
default
default
default
default
default
default
default
default


3、管道ch的缓冲区为10select中有case读取管道的数据

代码示例

func main() {
	ch := make(chan int)
	go func() {
		for i := 0; i < 20; i++ {
			select {
			case ch <- 1:
				fmt.Println("输入1")
			case ch <- 2:
				fmt.Println("输入2")
			case <-ch:
				fmt.Println("输出")
			default:
				fmt.Println("default")
			}
		}
	}()

	time.Sleep(time.Millisecond)
}

输出结果

default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default

4、管道ch的缓冲区为10,select中有case读取管道的数据

代码示例

`func main() {
	ch := make(chan int)
	go func() {
		for i := 0; i < 20; i++ {
			select {
			case ch <- 1:
				fmt.Println("输入1")
			case ch <- 2:
				fmt.Println("输入2")
			default:
				fmt.Println("default")
			}
		}
	}()

	time.Sleep(time.Millisecond)
}

输出结果

default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default
default

5、补充知识:channel底层的等待队列

在runtime包中hchan定义了管道的数据结构,里面有recvq和sendq这两个等待队列。

type hchan struct {
	```
	recvq waitq 								// 等待读消息的协程队列
	sendq waitq								// 等待写消息的协程队列
	
	```
}

(1)协程阻塞,加入等待队列

  • 从管道「读取数据」时,如果「管道缓冲区为空」或「没有缓冲区」,则当前协程会被阻塞,并被加入recvq队列。
  • 向管道「写入数据」时,如果「管道缓冲区已满」或「没有缓冲区」,则当前协程会被阻塞,并被加入sendq队列。

举例:下面展示了一个没有缓冲区的管道,有几个协程阻塞等待读数据。

在这里插入图片描述

(2)协程被唤醒:

  • 因「读阻塞的协程」会被「向管道写入数据的协程」唤醒;
  • 因「写阻塞的协程」会被「从管道读取数据的协程」唤醒。

处于等待队列中的协程会在其他协程操作管道时被唤醒。
:为什么说是其他协程呢?
: 加入到等待队列中的协程应该是阻塞的才对,不管是读操作阻塞还是写操作阻塞,在同一个协程程序中后面的读写操作都是无法执行的,这个时候就只能由其他协程来写或读帮助唤醒这个协程,如果没有其他协程帮忙唤醒,就会出现死锁。

如果没有缓冲区的情况下,在同一个协程内进行写和读,因为没有其他协程去读这个管道,就会一直阻塞写这一步,从而出现死锁;

如果有缓冲区并且缓冲区未满的情况下,因为有缓冲区帮忙缓冲数据,所以在同一个协程内进行读和写是没有问题的。

(3)同一个协程里面,管道无缓冲区,会死锁

代码示例

func main() {
	ch := make(chan int)
	ch <- 1

	fmt.Println(<-ch)

	time.Sleep(time.Millisecond)
}

输出结果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /Users/dns/GolandProjects/code/select/demo01.go:10 +0x37
exit status 2

底层示意图

没有缓冲区的情况

(4)同一个协程里面,管道有缓冲区,不会死锁

代码示例

func main() {
	ch := make(chan int, 10)
	ch <- 1
	
	fmt.Println(<-ch)

	time.Sleep(time.Millisecond)
}

输出结果

1

底层示意图

请添加图片描述

注意:

① 一般情况下,recvq 和 sendq 至少有一个为空(因为对于同一个管道而言,如果有读协程的话,那么等待队列中就不会有写协程,如果是有读有写,协程就不会阻塞,就不会被添加到等待队列中)。

特殊情况:同一个协程使用select语句向管道一边写入数据,一边读取数据,此时协程就会分别位于两个等待队列中。
具体来说就是:

  • 在一个协程中用select去监听「该协程向一个管道写入数据」,并且同时去监听「该协程向同一个管道读取数据」;
  • 该协程会位于等待队列中,是因为读写操作阻塞了;
  • 该协程会同时位于两个等待队列中,是因为select有多路复用的机制,能够同时监听多个case,同一个协程进行多个读写操作阻塞,自然就会同时放在两个等待队列中;
  • 假设读操作不阻塞的话,也就是其他协程中有对该管道写入数据,那么recvq等待队列里面就会把该协程移除,该协程就自然不会放在recvq等待队列中,select也就能够去随机执行一个case。

6、补充知识:select

  1. select功能:解决多个管道的选择问题,也可以叫多路复用,可以从多个管道中随机公平地选择一个来执行
  2. case后面必须进行的是io操作,不能是等值,随机去选择一个io操作
  3. default防止select被阻塞住,加入default

7、总结

  1. 同一个协程中,select监听同一个channel的和写,如果该channel有缓冲区,那么for循环下,select会随机执行读或写的case,一边取出一边放入,这样缓冲区就不会满了;
  2. 同一个协程中,select监听同一个channel的没有读,如果该channel有缓冲区,那么for循环下,select会随机执行读或写的case,只有放入,这样缓冲区就会容易写满而导致写操作阻塞,最后只能执行default;
  3. 同一个协程中,select监听同一个channel的和写,如果该channel无缓冲区,那么此时管道既不能读也不能写,所以会直接输出default(如果没有default,select就会陷入阻塞,直到任意一个管道解除阻塞;有了default,select就是非阻塞的);
  4. 同一个协程中,select监听同一个channel的没有读,如果该channel无缓冲区,此时管道也不能读,所以会直接输出default。

三、例子2

代码示例

var channels = [3]chan int{
	nil,
	make(chan int),
	nil,
}

var numbers = []int{1, 2, 3}

func main() {
	time.Sleep(time.Millisecond)
	select {
	case getChan(0) <- getNumber(0):
		fmt.Println("The first candidate case is selected.")
	case getChan(1) <- getNumber(1):
		fmt.Println("The second candidate case is selected.")
		fmt.Println("")
	case getChan(2) <- getNumber(2):
		fmt.Println("The second candidate case is selected.")
		fmt.Println("")
	default:
		fmt.Println("No candidate case is selected.")
	}
}

func getNumber(i int) int {
	fmt.Printf("numbers[%d]\n", i)
	return numbers[i]
}

func getChan(i int) chan int {
	fmt.Printf("channels[%d]\n", i)
	return channels[i]
}

输出结果

channels[0]
numbers[0]
channels[1]
numbers[1]
channels[2]
numbers[2]
No candidate case is selected.

select 语句的执行顺序

  1. 从上到下完成所有case后面的表达式
  2. case后面的表达式是从左往右依次执行
    因此控制台会依次输出所有channels[]、numbers[]
  3. 等所有表达式执行完成后,才会执行候选语句;
    执行default,是因为case后面的表达式因为阻塞,而导致条件不成立。具体来说就是:第一个和第三个case管道值为nil,所以不管是读还是写管道,都会阻塞;第二个case是向无缓冲区的管道中写入数据,所以需要有其他协程来帮忙从管道中读数据,因为没有其他协程的帮忙,所以会阻塞。因而最终执行成功的case就只有default,其他的case都只是执行了case后面附带的表达式而已。

四、参考资料

  1. 《Go专家编程》
  2. 马士兵教育 【协程与管道】select功能

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

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

相关文章

基于stm32物联网开发板(2)--LCD屏幕

基于stm32物联网开发板(2)–LCD屏幕 LCD应用展示&#xff1a; LCD屏幕应用 1.概述 屏幕尺寸为1.3寸&#xff0c;分辨率240*240&#xff0c;颜色格式RGB565&#xff0c;驱动IC&#xff1a;ST7789VW&#xff1b;超大可视角度:大于160(显示屏中可视角度最大的一种屏幕)&#xff1…

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的&#xff1f;方法句柄 1.方法句柄出现的原因 某个国家举办了跑步比赛&#xff0c;有亚洲&#xff0c;欧洲还是非洲人参赛,但是有机器人也参赛了。机器人不属于人类阵营&#xff0c;怎么能让机器人也参加进来呢&#xff1…

Java递归生成树

1.建菜单表 CREATE TABLE t_menu ( id int(11) NOT NULL AUTO_INCREMENT, pid int(11) NOT NULL, name varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT11 DEFAULT CHARSETutf8mb4; 2.造一些数据 注意&#xff1a;根节点的pid0&#xff0c…

利用Rsoft开展弯曲光纤仿真分析

Rsoft是一款优秀的光学仿真软件&#xff0c;里面集成了多个模块&#xff0c;其中BPM模块利用光束传播法&#xff08;Beam Propagation Method&#xff09;&#xff0c;能够进行多种类型光器件的仿真&#xff0c;比如分束器、光纤等。这次&#xff0c;利用该模块展示如何开展光纤…

SpringSecurity简单的练手项目(SpringBoot+SpringSecurity+JWT)

文章目录 一、项目介绍二、SpringSecurity简介SpringSecurity中的几个重要组件&#xff1a;1.SecurityContextHolder&#xff08;class&#xff09;2.SecurityContext&#xff08;Interface&#xff09;3.Authentication&#xff08;Interface&#xff09;4.AuthenticationMana…

Eclipse的介绍与安装

Eclipse简介 Eclipse 是一个开放源代码的&#xff0c;基于 Java 的可扩展开发平台。Eclipse官方版是一个集成开发环境(IDE)&#xff0c;可以通过安装不同的插件实现对其它计算机语言编辑开发&#xff0c;如C、Php、Python等等。 Eclipse的下载 下载时需要访问网址 http://…

Android系统原理性问题分析 - RefBase、sp、wp 分析

声明 在Android系统中经常会遇到一些系统原理性的问题&#xff0c;在此专栏中集中来讨论下。接触Android系统&#xff0c;遇到很多sp、wp相关问题&#xff0c;此篇分析Android系统内的智能指针问题。此篇参考一些博客和书籍&#xff0c;代码基于Android 9.0.0&#xff0c;不方…

3D点云的基本操作(基于PCL编程)

知识储备 右手系 右手&#xff0c;拇指&#xff0c;食指&#xff0c;中指&#xff0c;分别是x,y,z的正方向。左手系则同理。 旋转矩阵 本质&#xff1a;两个坐标系之间的旋转关系。 用途&#xff1a;旋转点云。 原理&#xff1a;设传感器的坐标系为O1X1Y1Z1&#xff0c;设…

mysql 分组语句测试

建表 建表语句&#xff1a; CREATE TABLE student( id int not null, name char(12), sex char(1) ); 预置数据 insert into student values(1, wh, 1); insert into student values(2, wh1, 0); insert into student values(3, zyx, 0); commit; 增加字段 alt…

设计模式的分类、意图和适用性

文章目录 引言分类创建型设计模式Factory Method&#xff08;工厂方法&#xff09;Abstract Factory&#xff08;抽象工厂&#xff09;Builder&#xff08;生成器&#xff09;Prototype&#xff08;原型&#xff09;Singleton&#xff08;单例&#xff09; 结构型设计模式Adapt…

【二】设计模式~~~创建型模式~~~工厂方法模式(Java)

【学习难度&#xff1a;★★☆☆☆&#xff0c;使用频率&#xff1a;★★★★★】 2.1. 模式动机 现在对该系统进行修改&#xff0c;不再设计一个按钮工厂类来统一负责所有产品的创建&#xff0c;而是将具体按钮的创建过程交给专门的工厂子类去完成&#xff0c;我们先定义一个…

【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…

洗地机哪个品牌好?好用的家用洗地机分享

洗地机采用高效吸力和清洗方式&#xff0c;可快速清除地面污渍和痕迹&#xff0c;让地面干净整洁&#xff0c;提高使用者的生活品质和舒适度。洗地机不仅清洁效果好&#xff0c;而且操作简单&#xff0c;大多采用一键启动和一键停止&#xff0c;方便快捷&#xff0c;节省时间和…

MySQL备份工具之xtrabackup

文章目录 MySQL备份工具之xtrabackup一、xtrabackup的介绍1、xtrabackup 版本兼容性2、Xtrabackup优点3、Xtrabackup备份原理 二、安装mysql5.7.x1、yum方式安装mysql5.7.x的方式2、下载 xtrabackup2.4 版本3、xtrabackup2.4备份mysql5.7.x数据3.1、innobackupex全备3.2、模拟数…

瑞吉外卖 - 完善后台系统登陆功能(5)

某马瑞吉外卖单体架构项目完整开发文档&#xff0c;基于 Spring Boot 2.7.11 JDK 11。预计 5 月 20 日前更新完成&#xff0c;有需要的胖友记得一键三连&#xff0c;关注主页 “瑞吉外卖” 专栏获取最新文章。 相关资料&#xff1a;https://pan.baidu.com/s/1rO1Vytcp67mcw-PD…

北邮22信通:电子电路实验:分享一个存放零散电阻的小方法

北邮22信通一枚~ 很高兴以一个新身份和大家见面&#xff01; 有关电子电路实验的新专栏即将开启&#xff0c;会尽量分享一些实验报告方面的文章&#xff0c;大家敬请期待~ 这篇文章想和大家分享困扰小编好久的问题的解决方法&#xff01;同时也就作为专栏开启的引子啦~ 事…

DJI A3飞控 遥控器信号中断 会导致什么问题?

DJI A3飞控 遥控器信号中断 会导致什么问题&#xff1f; 在使用DJI A3 飞控的过程中&#xff0c;希望用OSDK完成自动化的任务。 DJI A3要求必须连接遥控器&#xff0c;可以是大疆Lightbridge的遥控器&#xff0c;也可以是SBUS协议的遥控器&#xff0c;比如航模的支持SBUS协议的…

【历史上的今天】4 月 17 日:Turbo Pascal 2.0 发布;PlayStation 遭受攻击;搜狐李善友辞职

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 17 日&#xff0c;在 1790 年的今天&#xff0c;电学奠基人富兰克林逝世。美国的杰出发明家本杰明富兰克林从 1746 年开始研究电的现象&#xff0c;通过反…

问卷调查设计攻略!这些原则步骤让你的结果更精准

调查问卷是从特定人群中收集数据的有效工具。在设计调查问卷时&#xff0c;我们必须仔细考虑研究目标、目标受众和所需信息的类型。调查问卷的设计原则和步骤对于确保所收集数据的准确性和可靠性非常重要。在本文中&#xff0c;我们将讨论问卷的设计原则和步骤。 一、问卷设计…

Vivado输入输出时序约束(set_input_delay、set_output_delay)

前言 I/O Delay约束主要有两个命令&#xff1a;set_input_delay和set_output_delay。 I/O Delay约束的主要目的同时钟约束一样&#xff0c;是告诉编译器&#xff0c;外部输入输出信号与参考时钟之间的相位关系&#xff0c;便于综合器能够真实和准确的对IO接口的信号进行…