12.2 通道-阻塞与流程控制、通道型函数、退出通道

阻塞与流程控制

通常在并发程序中要尽力避免阻塞式操作,但有时又需要让代码暂时处于阻塞状态,以等待某种条件、信号或数据,然后再继续运行。

对于无缓冲通道,试图从无人写入的通道中读取,或者向无人读取的通道中写入,都会引起阻塞。

重点:利用无缓冲通道的阻塞I/O,可以很容易地在异步执行的多个Goroutine之间构建同步化的流程控制。

// 基于通道的流程控制
// 试图从无人写入的通道中读取,或者向无人读取的通道中写入,都会引起阻塞,利用
// 这一特性可在多"线程"之间建立某种"停——等"机制
// 在下例中,模拟了一个电子时间,每隔1s更新显示一次时间。在父线程中先于子线程写入而读取,产生阻塞;子线程先于父现场而写入,也会产生阻塞。
package mainimport ("fmt""time"
)func clock(c chan string) {ticker := time.NewTicker(time.Second) // 定时器,周期为1sfor { // 死循环t := <-ticker.C 	// C是一个chnnel,每间隔一个定时周期,可以从通道内// 度取1个时间信息// 格式化时间展示格式并写入传入函数的channel cc <- t.Format("02 Jan 2006 15:04:05")	}
}func main() {c := make(chan string)go clock(c)for {message := <-cfmt.Printf("\r%v", message)}
}

 通道型函数参数(只读、只写、可读可写)

可将通道作为参数传递给函数,并在其类型中指明该通道型参数是只读的、只写的,还是既可读又可写的。

func channelReader(c <-chan string) { // 只读通道message := <-c
}
func channelWriter(c chan<- string) { // 只写通道c <- "Hello World!"
}
func channelReaderAndWriter(c chan string) { // 可读写通道message := <-cc <- "Hello World!"
}

通过指定通道型参数的读写权限,有助于确保通道中数据的完整性,同时指定程序的哪部分可向通道发送数据,哪部分又能从通道接收数据。

select语句

在并发式编程中,经常需要利用多个通道,同时与多个Goroutine建立通信。

顺序遍历来自多个通道的消息显然并非好的设计,因为仅一个通道的阻塞就会影响对其它所有通道消息的处理,例如:

for {msg1 := <-c1 // fmt.Println(msg1)msg2 := <-c2 // fmt.Println(msg2)
}

假设负责向c1通道写入数据的"子线程"由于某种原因发生了阻塞,没能及时地写入数据,"父线程"将阻塞在从c1通道读取数据的语句,这时负责向c2通道写入数据的另外一个"子线程"将因为c2通道无人读取而发生写阻塞。这种因为一个"线程"发生阻塞导致所有"线程"都跟着一起阻塞的运行模式,显然有悖于并发式编程的设计初衷,应当着意避免。

// 多通道I/O(错误实例:顺序遍历)
// 顺序遍历来自多个通道的消息显然并非好的设计,因为
// 仅一个通道的阻塞就会影响对其它所有通道消息的处理
package mainimport ("fmt""time"
)func proc(c chan rune, ch rune,	// rune类型,unicode编码等价于int32ms time.Duration) {for {c <- ch time.Sleep(ms * time.Millisecond)}
}func main() {c1 := make(chan rune)c2 := make(chan rune)go proc(c1, '-', 100)	// 初衷:每100ms,打印1个-go proc(c2, '+', 500) // 初衷:每500ms,打印1个+for {ch := <-c1fmt.Printf("%c", ch)ch = <-c2fmt.Printf("%c", ch)}
}
// 打印输出:
// +-+-+-+-+-+-+-+-+-+-+-+-+ 
// 实际情况,两个现场都是按照500ms的时间间隔来打印的,其原因在于两个channel/// 的读取数据都发生在同一个线程中,且二者是顺序执行的关系,c2阻塞时,c1也无法
// 执行。

select语句为多个通道创建了一系列接收者,哪个通道有消息被写入先接收哪个通道。

for {select {case message := <-c1: // fmt.Println(message)case message := <-c2: // fmt.Println(message)}
}

"父线程"中的select语句以阻塞方式,同时监视连接着多个"子线程"的多个通道,无论哪个"子线程"向其所持有的通道写入了数据,select语句都会立即有所察觉,并根据先到先得的原则,匹配到与发生写入动作的通道相对应的case分支,读取该通道中的数据。

// 多通道选择 (前一示例的正确处理形式)
// select语句为多个通道创建了一系列接收者,
// 哪个通道先有消息被写入就先接收哪个通道
package mainimport ("fmt""time"
)func proc(c chan rune, ch rune,ms time.Duration) {for {c <- ch time.Sleep(ms * time.Millisecond)}
}
func main() {c1 := make(chan rune)c2 := make(chan rune)go proc(c1, '-', 100)go proc(c2, '+', 500)for {select {case ch := <-c1:fmt.Printf("%c", ch)case ch := <-c2:fmt.Printf("%c", ch)}}
}
// 打印输出:
// +-----+-----+-----+-----+ 

要想从多个通道中以最及时的方式接收并处理消息,select语句是个不错的选择,但如果所有的通道都没有消息呢?

  • select语句将会长时间甚至永远处于阻塞状态,这对于并发式编程同样是不利的。

可以设置一个超时时间,让select语句于指定的时间后解除阻塞,继续运行。

注:time包的After函数,其参数为某一时间值,该函数会返回1个channel。这个channel会在指定的参数时间之后,会有消息写入(一个时间消息)。

for {select {case message := <-c1:fmt.Println(message)case message := <-c2:fmt.Println(message)case <-time.After(time.Second): // 触发超时fmt.Println("反正也没消息,不如摸会鱼吧……╮(╯ω╰)╭ ")}
}

// 永久等待
// 若通道长时间无人写入,针对该 
// 通道的select语句将会一直阻塞
package main
import ("fmt""time"
)
func proc(c chan rune, ch rune,ms time.Duration) {for i := 0; ; { // 仅执行10次写入操作if i < 10 {c <- ch i++}time.Sleep(ms * time.Millisecond)}
}
func main() {c1 := make(chan rune)c2 := make(chan rune)go proc(c1, '-', 100) // 写10次-go proc(c2, '+', 500) // 写10次+for {select {case ch := <-c1:fmt.Printf("%c", ch)case ch := <-c2:fmt.Printf("%c", ch)}}
}
// 打印输出:
// +-----+-----++++++++ 
// 主线程在读取10个-与10个+后,就处于了永久阻塞状态。
// 等待超时
// 使用超时时间,可让select语句在长时间收不到消息的 
// 情况下不至于一直阻塞,可利用这段时间执行空闲处理
package main
import ("fmt""time"
)
func proc(c chan rune, ch rune,ms time.Duration) {for i := 0; ; {if i < 10 {c <- ch i++}time.Sleep(ms * time.Millisecond)}
}
func main() {c1 := make(chan rune)c2 := make(chan rune)go proc(c1, '-', 100)go proc(c2, '+', 500)for {select {case c := <-c1:fmt.Printf("%c", c)case c := <-c2:fmt.Printf("%c", c)case t := <-time.After(time.Second): // 触发超时,1sfmt.Printf("\n%s> Timeout!",t.Format("2006/01/02 15:04:05"))// ……还应有相应的退出循环,退出通道等善后操作}}
}
// 打印输出:
// +-----+-----++++++++
// 2020/01/04 16:45:57> Timeout!

退出通道

通过设置超时时间,固然可以解除处于阻塞状态的select语句,但有时解除阻塞的条件也许并不是时间。

为select语句添加一个退出通道,通过向退出通道发送消息解除select阻塞。

stop := make(chan bool)
go func() {if 消息循环可以退出了 {stop <- true}
}()
escape := false
for !escape { // 消息循环select {... // 处理各种消息case <-stop:escape = true}
} 
// 退出通道(给电子时钟的实例添加退出通道操作)
// 为select语句添加退出通道,向退出通道发送消息以结束select循环
package main
import ("fmt""time"
)
func clock(channel chan string) {ticker := time.NewTicker(time.Second)for {t := <-ticker.C channel <- t.Format("02 Jan 2006 15:04:05")}
}
func main() {work := make(chan string)stop := make(chan bool)go clock(work)go func() {time.Sleep(10 * time.Second) // 10s后关闭消息循环stop <- true}()escape := falsefor !escape {select {case message := <-work:fmt.Printf("\r%v", message)case <-stop:	// 退出通道escape = true}}fmt.Println("\nTime's up!")
}
// 打印输出:
// 04 Feb 2020 18:00:48
// Time's up! 

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

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

相关文章

Redis教程(二十一):Redis怎么保证缓存一致性

传送门:Redis教程汇总篇,让你从入门到精通 Redis 的缓存一致性 Redis 的缓存一致性是指在使用 Redis 作为缓存层时,保证缓存中的数据与数据库中的数据保持一致的状态。在分布式系统中,数据一致性是一个重要的问题,因为可能存在多个客户端同时读写同一数据,或者数据在不同…

【量算分析工具-贴地面积】GeoServer改造Springboot番外系列十

【量算分析工具-概述】GeoServer改造Springboot番外系列三-CSDN博客 【量算分析工具-水平距离】GeoServer改造Springboot番外系列四-CSDN博客 【量算分析工具-水平面积】GeoServer改造Springboot番外系列五-CSDN博客 【量算分析工具-方位角】GeoServer改造Springboot番外系列…

SQL学习小记(三)

SQL学习小记&#xff08;三&#xff09; 功能实现思路代码部分名词解释 代码打包为可执行文件 功能说明&#xff1a;使用python代码&#xff0c;将数据库database1中的表格table1同步到数据库database2中 功能实现 思路 #mermaid-svg-R1pWrEWA799M299a {font-family:"tre…

go语言方法之基于指针对象的方法

当调用一个函数时&#xff0c;会对其每一个参数值进行拷贝&#xff0c;如果一个函数需要更新一个变量&#xff0c;或者 函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝&#xff0c;这种情况下我们就需 要用到指针了。对应到我们这里用来更新接收器的对象的方法…

域名服务器是什么?

所谓域名服务器&#xff08;即Domain Name Server&#xff0c;简称Name Server、DNS&#xff09;实际上就是装有域名系统的主机。它是一种分层结构数据库&#xff0c;能够执行域名解析。

Rocksdb原理简介

100编程书屋_孔夫子旧书网 Rocksdb作为当下nosql中性能的代表被各个存储组件&#xff08;mysql、tikv、pmdk、bluestore&#xff09;作为存储引擎底座&#xff0c;其基于LSM tree的核心存储结构&#xff08;将随机写通过数据结构转化为顺序写&#xff09;来提供高性能的写吞吐时…

Google Benchmark库 简介

在C中&#xff0c;进行性能测试&#xff08;Benchmarking&#xff09;是一个常见的需求&#xff0c;用以测量代码块的执行时间&#xff0c;从而对代码进行优化。Google Benchmark库是一个广泛使用的C库&#xff0c;专门用于编写稳健的基准测试。以下是如何使用Google Benchmark…

Visual Studio 的使用

目录 1. 引言 2. 安装和配置 2.1 系统要求 2.2 安装步骤 2.3 初次配置 3. 界面介绍 3.1 菜单栏和工具栏 3.2 解决方案资源管理器 3.3 编辑器窗口 3.4 输出窗口 3.5 错误列表 3.6 属性窗口 4. 项目管理 4.1 创建新项目 4.2 导入现有项目 4.3 项目属性配置 5. 代…

C++之using

using是C11引入的关键字 1、类型别名 using可以用来为类型创建一个新的名字&#xff0c;方便代码维护。 // 定义类型别名 using data_type short;// 使用 data_type value 5;2、命名空间别名 using可以为非常长的命名空间创建一个别名&#xff0c;方便使用。 namespace ve…

当传统文化遇上数字化,等级保护测评的必要性

第二十届中国&#xff08;深圳&#xff09;国际文化产业博览交易会5月23日在深圳开幕。本届文博会以创办20年为契机&#xff0c;加大创新力度&#xff0c;加快转型升级&#xff0c;着力提升国际化、市场化、专业化和数字化水平&#xff0c;不断强化交易功能&#xff0c;打造富有…

《软件方法(下)》8.3.4.6 DDD话语“聚合”中的伪创新(1)

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 8.3 建模步骤C-2 识别类的关系 8.3.4 识别关联关系 8.3.4.6 DDD话语“聚合”中的伪创新 DDD话语中也有“聚合”。Eric Evans的“Domain-Driven Design: Tackling Complexity in the…

在今日头条上写文章:ChatGPT完整使用教程

了解如何充分运用ChatGPT进行创作 简介 在今日头条上发布文章变得越来越方便。本文旨在详细解析如何运用ChatGPT来创作文章&#xff0c;并提供全方位的使用指南及常见问题的答疑。 第一步&#xff1a;基础准备 确保你已注册今日头条账号。 登录ChatGPT并与你的今日头条账号进…

华为、华三、思科、锐捷交换机路由器设备命令行常见错误信息

目的 使用CLI命令行界面&#xff0c;如果输入错误&#xff0c;系统将会向用户报告错误信息。 知己知彼&#xff0c;方能百战不殆。下面是小木为大家收集整理的几大厂商常见的命令行错误提示。 华为设备命令行常见错误信息 Error: Unrecognized command found at ^ position. …

Java中的CAS(Compare-And-Swap)操作详解

一、技术难点 CAS操作&#xff0c;全称为Compare-And-Swap&#xff0c;是Java并发编程中的一个重要概念&#xff0c;其技术难点主要体现在以下几个方面&#xff1a; 原子性保证&#xff1a;CAS操作必须保证整个操作的原子性&#xff0c;即在多线程环境中&#xff0c;当一个线…

mysql 删除重复数据 关联自己 关联子查询 delete

有手工录入的数据时&#xff0c;删除系统定时任务计算的数据。 delete from t1 using data_tab as t1, (select * from (select input_type,system_code,DATE_FORMAT(start_time,%Y-%m-%d) as date_ from data_tab where start_time >2024-05-20 and start_time <2024-…

软件测试经理工作日常随记【6】-利用python连接禅道数据库并自动统计bug数据到钉钉群

测试管理_利用python连接禅道数据库并统计bug数据到钉钉 这篇不多赘述&#xff0c;直接上代码文件。 另文章基础参考博文&#xff1a;参考博文 加以我自己的需求优化而成。 统计的前提 以下代码统计的前提是禅道的提bug流程应规范化 bug未解决不删除bug未关闭不删除 db_…

Paddle 0-d Tensor 使用指南

Paddle 0-d Tensor 使用指南 1. 0-d Tensor 的定义 在深度学习框架中&#xff0c;Tensor 是存储和操作数据的基本数据结构。一个 Tensor 可以有 0 到任意多的维度,每个维度对应一个 shape 值。而 0-d Tensor&#xff0c;顾名思义&#xff0c;就是一个无任何维度的 Tensor&…

Oracle 数据泵(Data Pump)的impdp解析

impdp 是 Oracle 数据泵&#xff08;Data Pump&#xff09;用于数据导入的命令行工具。 directory: 指定转储文件和日志文件所在的操作系统目录对象名。此目录对象必须事先使用 CREATE DIRECTORY 命令创建并在数据库中定义。 impdp system/password directorydir_name ...dumpf…

LuatOS学习

开发顺序 Lua是脚本语言中运行速度最快的语言 资源占用极低 脚本语言运行方式 脚本语言是从上往下一行一行运行的 变量 coun 123456 a,b,c 1,2,3交换 a,b b,a在测试环境中&#xff0c;用print(a,b)打印 nil类型 未声明的变量就是nil&#xff0c;nil用来表示此变量为空…

STM32高级控制定时器(STM32F103):检测输入PWM周期和占空比

目录 概述 1 PWM 输入模式 1.1 原理介绍 1.2 应用实例 1.3 示例时序图 2 使用STM32Cube配置工程 2.1 软件环境 2.2 配置参数 2.3 生成项目文件 3 功能实现 3.1 PWM占空比函数 3.2 输入捕捉回调函数 4 功能测试 4.1 测试软件框架结构 4.2 实验实现 4.2.1 测试实…