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

news2024/11/15 21:43:40

阻塞与流程控制

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

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

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

// 基于通道的流程控制
// 试图从无人写入的通道中读取,或者向无人读取的通道中写入,都会引起阻塞,利用
// 这一特性可在多"线程"之间建立某种"停——等"机制
// 在下例中,模拟了一个电子时间,每隔1s更新显示一次时间。在父线程中先于子线程写入而读取,产生阻塞;子线程先于父现场而写入,也会产生阻塞。
package main

import (
    "fmt"
    "time"
)

func clock(c chan string) {
    ticker := time.NewTicker(time.Second) // 定时器,周期为1s

    for { // 死循环
        t := <-ticker.C 	// C是一个chnnel,每间隔一个定时周期,可以从通道内
							// 度取1个时间信息
		// 格式化时间展示格式并写入传入函数的channel c
        c <- t.Format("02 Jan 2006 15:04:05")	
    }
}

func main() {
    c := make(chan string)

    go clock(c)

    for {
        message := <-c
        fmt.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 := <-c
        c <- "Hello World!"
}

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

select语句

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

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

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

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

// 多通道I/O(错误实例:顺序遍历)
// 顺序遍历来自多个通道的消息显然并非好的设计,因为
// 仅一个通道的阻塞就会影响对其它所有通道消息的处理
package main

import (
    "fmt"
    "time"
)

func proc(c chan rune, ch rune,	// rune类型,unicode编码等价于int32
    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)	// 初衷:每100ms,打印1个-
    go proc(c2, '+', 500) // 初衷:每500ms,打印1个+

    for {
        ch := <-c1
        fmt.Printf("%c", ch)

        ch = <-c2
        fmt.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 main

import (
    "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): // 触发超时,1s
            fmt.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 := false
    for !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.coloradmin.cn/o/1711702.html

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

相关文章

学习笔记——数据通信基础——数据通信网络(拓扑结构)

网络拓扑 网络拓扑(Network Topology)是指用传输介质(例如双绞线、光纤等)互连各种设备(例如计算机终端、路由器、交换机等)所呈现的结构化布局。 1、网络拓扑形态 星型网络∶所有节点通过一个中心节点连接在一起。 优点∶容易在网络中增加新的节点。通信数据必须经过中心节点…

学习Uni-app开发小程序Day21

学习了评分组件、自定义导航栏 评分组件uni-rate 这是需要达到的效果图&#xff0c;这里先分析下效果图&#xff0c; 1、图片是从布局中间弹出的&#xff0c;那这里就要用到uni-popup &#xff0c;设置type从中间弹出 2、这个弹出的顶部和上一张的顶部布局是一样的&#xff0c…

C# 反射GetProperties和GetFields的坑

有时候使用反射&#xff0c;获取类的所有字段和所有属性&#xff0c;一般情况下是按照我们写的先后顺序返回的。 但是我今天碰到了一次不是按照顺序返回的&#xff01;&#xff01;&#xff01; 翻看文档&#xff1a; GetProperties&#xff1a; https://learn.microsoft.com/…

【C++】从零开始构建红黑树 —— 节点设计,插入函数的处理 ,旋转的设计

送给大家一句话&#xff1a; 日子没劲&#xff0c;就过得特别慢&#xff0c;但凡有那么一点劲&#xff0c;就哗哗的跟瀑布似的拦不住。 – 巫哲 《撒野》 &#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b;&#x1f30b; ⛰️⛰️…

WordPress建网站公司 建易WordPress建站

建易WordPress建网站公司是一家专业从事WordPress网站建设、网站维护、网站托管、运营推广和搜索引擎优化(SEO)等服务的公司。建易WordPress建网站公司提供多种服务&#xff0c;包括模板建站和定制网站&#xff0c;并且明码标价&#xff0c;价格透明&#xff0c;竭诚为全国各地…

常见5大开发进度盲点问题及解决方案

在软件开发项目中&#xff0c;识别并解决常见的进度管理盲点问题&#xff0c;对于确保项目按时、按预算、高质量完成至关重要。它直接关系到项目能否顺利进行&#xff0c;忽视任何一个问题&#xff0c;都可能导致项目延期、成本超支、质量下降&#xff0c;甚至项目失败。 因此&…

G60-M60F-ZQ手动抓取快速接头,专用于吊装设备的重物快速抓取

客户需求概述&#xff1a; 客户需要将重达将近400公斤的产品从一个工作台移动至另一个工作台&#xff0c;目前的方法是通过人工将吊环的螺纹与产品的螺纹相互拧紧&#xff0c;然后利用装备吊起移动&#xff0c;但这种方式效率低下&#xff0c;且因为工人的操作有时难以达到理想…

CHIMA专访美创高级总监丁斐:为医疗数据安全构筑体系化防御新机制

5月17-19日&#xff0c;中国医院信息网络大会&#xff08;CHIMA 2024&#xff09;在南京隆重召开。作为结识多年的老友&#xff0c;美创科技再携以数据为中心的全系列安全业务、新一代数字化安全平台、医疗行业解决方案精彩亮相。 会议期间&#xff0c;CHIMA专访美创科技&…

Linux之sshpass命令

介绍 sshpass是一个工具&#xff0c;用于通过SSH连接到远程服务器时自动输入密码。它允许您在命令行中指定密码&#xff0c;以便在建立SSH连接时自动进行身份验证。 安装 # 以centos为例 yum install sshpass -y 使用方法 sshpass [-f filename | -d num | -p password | …

精酿啤酒:品质与口感在啤酒品牌形象建设中的作用

啤酒品牌形象建设是提升市场竞争力的关键&#xff0c;而品质与口感在其中扮演着重要的角色。对于Fendi club啤酒而言&#xff0c;其卓着的品质和与众不同的口感在品牌形象建设中发挥了积极的作用。 品质是啤酒品牌形象的核心要素。消费者对啤酒品质的要求越来越高&#xff0c;品…

新书推荐:7.5 goto、break、continue语句

本节必须掌握的知识点&#xff1a; 示例二十六 代码分析 汇编解析 示例二十七 代码分析 汇编解析 7.5.1 示例二十六 ■goto语句&#xff1a;无条件转移语句。 语法格式&#xff1a; goto label; label : 代码; ●语法解析&#xff1a; 执行到goto语句时&#xff0c;则无…

【PB案例学习笔记】-10 进度条使用

写在前面 这是PB案例学习笔记系列文章的第10篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gite…

【python】OpenCV—Tracking(10.2)

文章目录 BackgroundSubtractorcreateBackgroundSubtractorMOG2createBackgroundSubtractorKNN BackgroundSubtractor Opencv 有三种背景分割器 K-Nearest&#xff1a;KNN Mixture of Gaussian&#xff08;MOG2&#xff09; Geometric Multigid&#xff08;GMG&#xff09; …

酒店提前线上订房小程序源码系统 PHP+MySQL组合开发 源码开源可二开 带完整的安装代码包以及搭建教程

系统概述 随着移动互联网的普及&#xff0c;越来越多的人习惯通过手机进行酒店预订。传统的线下订房方式逐渐无法满足用户的需求&#xff0c;酒店提前线上订房小程序的出现成为必然趋势。该源码系统的开发旨在为酒店提供一个便捷、高效的线上订房平台&#xff0c;提升用户体验…

【Java】【python】leetcode刷题记录--双指针

双指针也一般称为快慢指针&#xff0c;主要用于处理链表和数组等线性数据结构。这种技巧主要涉及到两个指针&#xff0c;一个快指针&#xff08;通常每次移动两步&#xff09;和一个慢指针&#xff08;通常每次移动一步&#xff09;。快指针可以起到’探路‘的作用&#xff0c;…

2-EMMC启动及各分区文件生成过程

EMMC的使用比nand flash还是复杂一些&#xff0c;有其特有的分区和电器性能 1、启动过程介绍 跟普通nand或spi flash不同&#xff0c;uboot前面还有好几级 在vendor某些厂商的设计中&#xff0c;ATF并不是BOOTROM加载后的第一个启动镜像&#xff0c;可能是这样的&#xff1a; …

WPF/C#:理解与实现WPF中的MVVM模式

MVVM模式的介绍 MVVM&#xff08;Model-View-ViewModel&#xff09;是一种设计模式&#xff0c;特别适用于WPF&#xff08;Windows Presentation Foundation&#xff09;等XAML-based的应用程序开发。MVVM模式主要包含三个部分&#xff1a;Model&#xff08;模型&#xff09;、…

启智CV机器人,ROS,ubuntu 20.04 【最后一步有问题】

资料&#xff1a; https://wiki.ros.org/kinetic/Installation/Ubuntu https://blog.csdn.net/qq_44339029/article/details/120579608 装VM。 装ubuntu20.04 desktop.iso系统。 装vm工具&#xff1a; sudo apt update sudo dpkg --configure -a sudo apt-get autoremove o…

一些关于深度聚类以及部分对比学习的论文阅读笔记

目录 资料SwAV问题方法方法的创新点为什么有效有什么可以借鉴的地方聚类Multi-crop 代码 PCL代码 Feature Alignment and Uniformity for Test Time Adaptation代码 SimSiam 资料 深度聚类算法研究综述(很赞&#xff0c;从聚类方法和深度学习方法两个方面进行了总结&#xff0…

windows ollama 指定模型下载路径

为Ollama指定模型的下载路径 在Windows系统中&#xff0c;如果想为Ollama指定模型的下载路径&#xff0c;可以通过设置环境变量来实现。以下是详细的步骤&#xff1a; 确定默认下载路径&#xff1a; 默认情况下&#xff0c;Ollama的模型可能会下载到C:\Users\<用户名>…