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

阻塞与流程控制

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

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

重点:利用无缓冲通道的阻塞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! 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝牙Mesh模块多跳大数据量高带宽传输数据方法

随着物联网技术的飞速发展&#xff0c;越来越多的设备需要实现互联互通。蓝牙Mesh网络作为一种低功耗、高覆盖、易于部署的无线通信技术&#xff0c;已经成为物联网领域中的关键技术之一。在蓝牙Mesh网络中&#xff0c;节点之间可以通过多个跳数进行通信&#xff0c;从而实现大…

广东商标协会批复为世界酒中国菜的指导单位

广东商标协会批复荐酒师公司成为“世界酒中国菜”活动指导单位 一、批复背景与意义 广东商标协会正式批复荐酒师国际认证&#xff08;广州&#xff09;有限公司&#xff0c;成为备受瞩目的“世界酒中国菜”系列活动的指导单位。该活动旨在共建“一带一路”倡议、助力“乡村振兴…

深度学习之CNN卷积神经网络

一.卷积神经网络 1. 导入资源包 import numpy as np import pandas as pd import matplotlib.pyplot as plt import sklearn import tensorflow as tf from tensorflow import keras注&#xff1a;from tensorflow import keras&#xff1a;从TensorFlow库中导入Keras模块&am…

第十三届蓝桥杯国赛大学B组填空题(c++)

A.2022 动态规划 AC; #include<iostream> #define int long long using namespace std; int dp[2050][15]; //dp[i][j]:把数字i分解为j个不同的数的方法数 signed main(){dp[0][0]1;for(int i1;i<2022;i){for(int j1;j<10;j){//一种是已经分成j个数,这时只需每一个…

Docker 快速更改容器的重启策略(Restart Policies)以及重启策略详解

目录 1. 使用 docker update 命令2. 在启动容器时指定重启策略3. 在 Docker Compose 文件中指定重启策略4. 总结 官方文档&#xff1a;Start containers automatically 1. 使用 docker update 命令 Docker 提供了 docker update 命令&#xff0c;可以在容器运行时更改其重启策…

Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋

&#x1f989; AI新闻 &#x1f680; Yann LeCun 和 Elon Musk 就 AI 监管激烈交锋 摘要&#xff1a;昨天&#xff0c;Yann LeCun 和Elon Musk 在社交媒体就人工智能的安全性和监管问题展开激烈辩论。LeCun 认为目前对 AI 的担忧和监管为时过早&#xff0c;主张开放和共享。而…

OrangePi AIpro初体验:开启嵌入式开发之旅

概述 随着物联网和智能设备时代的到来&#xff0c;单板电脑因其独特的优势成为创新项目和教育实践的重要工具。在众多单板电脑中&#xff0c;香橙派以其出色的性能和亲民的价格&#xff0c;十分吸引博主这初涉嵌入式开发的新手。博主有幸被CSDN邀请对OrangePi AIpro进行测评。…

css中实现背景方格

background: rgba(241,241,241,0.1); background-image:linear-gradient(90deg, rgba(241,243,244,1) 10%, transparent 0),linear-gradient(rgba()241,243,244,1 10%, transparent 0); background-size: 10px 10px; 表现出来的样子就是这个样子

广东海上丝绸之路文化促进会正式批复荐世界酒中国菜的指导单位

广东海上丝绸之路文化促进会正式批复成为“世界酒中国菜”系列活动指导单位 近日&#xff0c;广东海上丝绸之路文化促进会近日正式批复荐酒师国际认证&#xff08;广州&#xff09;有限公司&#xff0c;成为备受瞩目的“世界酒中国菜”系列活动的指导单位。此举旨在通过双方的…

Android Compose 八:常用组件 Switch

Switch 切换按钮 val isChecked remember { mutableStateOf(true) }Switch(checked isChecked.value,onCheckedChange {Log.i("text_compose","onCheckedChange>>"it)isChecked.value it})效果 默认颜色 应该对应 主题色 1.1 thumbContent 按钮…

盘点好用的国产传输软件,看看哪个适合你

流动让数据释放价值&#xff0c;无论什么企业&#xff0c;什么行业&#xff0c;业务的正常开展均是以数据和文件的传输为基础&#xff0c;因此&#xff0c;对企业来说&#xff0c;文件传输工具是最基础但也是最举重若轻的。在琳琅满目的多种国产传输软件中&#xff0c;哪个是最…

Java基础:基本语法(一)

Java基础&#xff1a;基本语法&#xff08;一&#xff09; 文章目录 Java基础&#xff1a;基本语法&#xff08;一&#xff09;1. 前言2. 开发环境搭建2.1 Java开发工具包下载2.2 环境变量配置2.3 Java程序的运行过程 3. 数据类型3.1 基本数据类型3.2 引用数据类型 4. 常量与变…

晓语台:基于大语言模型和深度学习技术的智能创作平台,高效、个性化地创作高质量内容。

晓语台 AI&#xff1a; 晓语台是由北京字里心间科技有限公司推出的一款智能AI写作工具。它基于百度的大语言模型和混合大模型以及AIGC技术研发而成&#xff0c;内置了多种风格和主题的AI创作模板&#xff0c;覆盖了20余类行业与职业&#xff0c;近30个海内外社交平台&#xff…

代码随想录——合并二叉树(Leetcode617)

题目链接 层序遍历 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) …

20240529代码沉思--------聊聊清单革命

以下内容取自百度&#xff1a; 清单革命 清单革命是一场观念革命&#xff0c;旨在通过列出清晰、明确的清单来避免犯错和提高效率。以下是关于清单革命的一些核心观点和原则&#xff1a; 核心观点&#xff1a; 人类的错误主要分为两类&#xff1a;“无知之错”和“无能之错…

java配置文件解析yml/xml/properties文件

XML 以mybatis.xml:获取所有Environment中的数据库并连接session为例 import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException;import javax.xml.parsers.DocumentBuilder; impo…

【软件设计师】网络与多媒体基础知识

1.多媒体网络 JPEG累进&#xff08;或增量、渐进、递增&#xff09;编码模式&#xff0c;实现图像内容的方式传输&#xff0c;在浏览器上的直观效果就是无需过久等待即可看到模糊图像&#xff0c;然后图像显示和内容由模糊逐渐变得清晰 GIF图像文件格式以数据块为单位来存储图像…

前端路由 Hash 模式和 History 模式

在SPA单页面模式盛行&#xff0c;前后端分离的背景下&#xff0c;我们要弄清楚路由到底是个什么玩意&#xff0c;它可以帮助我们加深对于前端项目线上运作的理解。 而现在我们常见的路由实现方式&#xff0c;主要有两种&#xff0c;分别是history和hash模式。 理解 如何理解路…

uart_tty_驱动程序框架

UART子系统(四) TTY驱动程序框架_tty驱动框架-CSDN博客

【网络层】ICMP 因特网控制协议

文章目录 ICMP 含义以及作用ICMP协议解析结合ICMP协议和ping常见问题 ICMP 含义以及作用 ICMP&#xff1a;Internet control massage protocol 因特网控制协议 Internet控制报文协议ICMP是网络层的一个重要协议。 ICMP协议用来在网络设备间传递各种差错和控制信息&#xff0c;…