草庐IT

go channel多路复用

岑吾 2023-03-28 原文

1. 为什么需要多路复用

Go 程序在并发处理一些任务的时,会为每一个任务创建一个 goroutine,然后需要根据不同的 goroutine 的返回的结果做不同的处理。
如果按照通常的做法,分别获取每个 channel 的结果:

taskCh1 := make(chan bool)
taskCh2 := make(chan bool)
taskCh3 := make(chan bool)

go run(taskCh1)
go run(taskCh2)
go run(taskCh3)

for {
   // 接收通道 1 的结果
   result1 := <-taskCh1
   // 接收通道 2 的结果
   result2 := <-taskCh2
   // 接收通道 3 的结果
   result3 := <-taskCh3
}

然后再根据不同 goroutine 返回的结果做后续的处理,这个代码有个问题,需要等待所有的 goroutine 都执行完成之后才能做出结果,这样实现的效率很低,因为每一个获取 channel 值的过程都是阻塞的。
在处理多个通道时,想同时接收多个通道的数据将会变的很困难。
而且在一些情况下,需要根据先返回通道的做出不同的处理,上面那种方式无法做到,这就需要使用多路复用。

Go 提供了 select 机制来解决这个问题。

2. select 基本使用

select 语法形式和 switch 很相似,switch 接收一个变量,然后根据变量的值做不同的处理,select 操作接收的是通道操作:

ch := make(chan int, 1) // 这个例子中,这里必须用缓冲通道

for {
    select {
    case <-ch:
        time.Sleep(time.Second)
        fmt.Println("case 1 invoke")
    case data := <-ch:
        time.Sleep(time.Second)
        fmt.Printf("case 2 invoke %d\n", data)
    case ch <- 100:
        time.Sleep(time.Second)
        fmt.Println("case3 invoke")
}

在 select 的 case 中,可以执行三种操作:

<- ch:接收通道,但是对值不处理
data := <-ch:接收通道,并处理从通道中得到的结果
ch <- 100:向通道中发送数据

上面的程序运行起来之后,case 3 会首先执行,然后 case1 和 case2 会随机执行一个,程序就这样一直交替运行下去。
如果用 select 改造上面第一个例子中的代码,就是下面这样:

for {
        select {
        // 接收通道 1 的结果
        case r := <-taskCh1:
            fmt.Printf("task1 result %+v\n", r)
        // 接收通道 2 的结果
        case r := <-taskCh2:
            fmt.Printf("task2 result %+v\n", r)
        // 接收通道 3 的结果
        case r := <-taskCh3:
            fmt.Printf("task3 result %+v\n", r)
        }
}

select 会及时响应每一个就绪的 channel,无论是发送数据还是接收数据。

3. 处理超时情况

select 除了用于同时处理多个通道之外,还可以用来处理一些通道超时的情况,通道在阻塞的时候,如果没有外界的干扰,会一直等下去,但是可以通过 select 设置一个超时时间,来打断阻塞:

ch := make(chan int, 1)

select {
case data := <- ch:
    fmt.Printf("case invoke %+v\n", data)
case <-time.After(3 * time.Second):
    fmt.Println("channel timeout")

}

上面的代码在创建了一个通道,但没有向通道中发送数据,如果不用 select,程序就会死锁。
select 中添加了两个 case,一个从通道中获取数据, 但肯定获取不到,所以在 3 秒钟之后,另一个 case 就会执行,返回通道超时的提示,这样就避免了程序会一直等待下去。
还有一个情况是我们有时候需用通过键盘获取其他输入设备向程序发送信号,也可以通过这种方式来实现,把上面的程序再修改一下:

ch := make(chan int, 1)

quitCh := make(chan bool, 1)

go func(ch chan bool) {
    var quit string
    fmt.Printf("quit? are you sure?: ")
    fmt.Scanln(&quit)
    quitCh <- true
}(quitCh)

select {
case data := <- ch:
    fmt.Printf("case invoke %+v\n", data)
case <-quitCh:
    fmt.Println("program quit")

}

这次不再通过超时来控制,而是通过键盘来控制,新建了一个通道,只有在键盘输入之后,才会向通道中发送数据,这样就可以做到自由控制程序的退出。

4. 非阻塞的 select

在上面的示例代码中,其实还少写了一部分,看下面的代码:

ch := make(chan int)

for {
    select {
    case <-ch:
        fmt.Println("case invoke")
    }
}

上面的代码会出现死锁,因为这个 select 只有一个 case,而这个 case 永远都不会接收到数据,所以 select 本身也被阻塞了,程序无法继续运行,就会造成死锁,对于这种情况,我们设置一个可用的 case,让 select 变成非阻塞,就可以解决这个问题。

ch := make(chan int)

for {
    select {
    case <-ch:
        fmt.Println("case invoke")
    default:
        time.Sleep(time.Second)
        fmt.Println("default invoke")
    }
}

这样,程序就不会死锁,而是不断的执行 default 中的内容。

5. 小结

在这篇文章中,我们介绍了通道的多路复用,并说明了可以用到多路复用的场景。下篇文章中,我们来详细聊一下 Go 是如何实现传统的并发模型。

本文转自:https://juejin.cn/post/6970302437571706911

有关go channel多路复用的更多相关文章

  1. 华为静态NAT、动态NAT、PAT端口复用 - 2

    一、网络环境及TOP1.1R1相当于内网的一台PC, IP:192.168.1.10 网关为 192.168.1.254[R1]iproute-static0.0.0.00192.168.1.254#R1配置默认路由(网关)1.2R2为出口路由器,分别连接内网R1及外网R31)R2 内网接口IP:192.168.1.2542)R2外网接口IP:100.1.1.102)R2NAT地址为:100.1.1.11-100.1.1.14二、静态NAT配置1.1静态NAT(一对一双向)R2配置静态NAT,将公网IP100.1.1.11映射到内网R1 192.168.1.10[R2]intg0/0/1[R2

  2. VHDL学习笔记——半加器 多路选择器 分频器 - 2

    VHDL程序结构:条件语句if_then_else_endif数据类型BIT类型(取逻辑位’1’或’0’)、整数类型INTEGER、布尔类型BOOLEAN(取TRUE或FALSE)、标准逻辑类型STD_LOGIC等进程语句与顺序语句process(敏感信号表)_endprocessVHDL中所有的顺序语句都必须放在进程语句中端口语句port(端口模式;端口数据类型);端口模式in:输入端口out:输出端口inout:双向端口buffer:缓冲端口关键字(不区分大小写)entity、architecture、end、if、else、in、out等;标识符(不区分大小写)自定义实体名、结构体名、端

  3. javascript - Reveal.js 多路复用不起作用 - 2

    我遵循以下链接中的示例:https://github.com/hakimel/reveal.js#multiplexing,但不知何故,多路复用不起作用——当母版幻灯片更新时,客户端不会更新。我已经在reveal.js演示socket.io服务器上进行了尝试,并尝试托管我自己的服务器。这些选项都不起作用,而且我很确定我已经正确配置了它们。这是我的配置代码:master/index.htmlReveal.initialize({controls:true,progress:true,history:true,center:true,multiplex:{id:'e2bc6e79f19fb

  4. graph - d3.js 中的网络多路由正交图 - 2

    我们想用d3画一个网络路由图,起始节点和结束节点是固定的,但中间的路径不同,可能共享一些节点,例如:我阅读了来自Configurefixed-layoutstaticgraphind3.js的评论并成功创建了一个简单的图形,如:但是当我向图中添加更多节点时,它变得随机(刷新后不是静态的)并且不再是正交的:所以我的问题是:是否可以使用d3.js来绘制接近所需的东西图形?或者有没有我应该在我的工作中使用的算法图实现? 最佳答案 在这里看我的演示。http://jsfiddle.net/doraeimo/JEcdS/主要思想是基于树建立连

  5. javascript - 使用 Promises 的多路流的正确模式 - 2

    所以最近几天我一直在玩promises,只是想转换一些项目,使用promises,但是我遇到这个问题不止几次。在阅读文章和教程时,一切看起来都很流畅和干净:getDataFromDB().then(makeCalculatons).then(getDataFromDB).then(serveToClient)但实际上并非如此。程序有很多改变整个流程的“if条件”:getDataFromCache(data).then(function(result){if(result){returnresult;}else{returngetDataFromDB();}}).then(functio

  6. javascript - 通过刷多路径图表无法绘制 d3.js Focus+Context - 2

    我已经用了几个星期了,但似乎无法弄清楚如何绘制下面的多路径图表。Focus+ContextviaBrushingchart我试图创建一个jsfiddle,但无法复制我得到的屏幕。在这一点上,我所拥有的与原始图表相似,只是只有一条路径而不是区域,并且刷牙工作正常。基本上尝试结合焦点图和多系列折线图Multiserieschart.但是,当我尝试添加另一条路径时,没有任何效果。请提出我需要做出的任何想法或更改以使其正常工作。还有其他类似的图表(或图表示例)我可以看一下吗?可以以任何方式或形式重新排列数据以使其起作用。Jsfiddlepath{fill:none;stroke:white;s

  7. javascript - 选项 "setupTestFrameworkScriptFile"被配置 "setupFilesAfterEnv"取代,它支持多路径 - 2

    Option"setupTestFrameworkScriptFile"wasreplacedbyconfiguration"setupFilesAfterEnv",whichsupportsmultiplepaths.Pleaseupdateyourconfiguration.我在这里找到了这个确切的问题:setupTestFrameworkScriptFileisnotsupportederror我将我的jest.config.js重命名为setUpTests.js但这并没有删除已弃用的错误警告。import{configure}from'enzyme'importAdapterf

  8. javascript - 同步事件多路分解如何成为繁忙等待的解决方案? - 2

    我正在尝试了解同步事件多路分解如何成为繁忙等待的解决方案。假设有3个IO操作,我们有一段代码不断循环检查这3个操作中是否有数据可供读取。arry=[event1,event2,event3]while(arryisnotempty){for(i=0;i上面的伪代码做了一个忙碌的等待。现在,在同步事件多路分解或react器模式中,事件监听器会在事件发生时对其做出响应。但是事件监听器如何在不忙等待的情况下做到这一点? 最佳答案 进程是已执行的计算机程序的实例(执行任务或模块)。在一个进程中,我们可以有多个称为线程的组件。您可以将线程想象

  9. CDM—码分复用(简单易懂) - 2

    码分复用一、简介二、CDMA原理2.1表示2.2如何选择码片序列正交的实现:三、流程图发送端接收端四、例题一、简介·码分复用简称CDM·可以实现多个用户同时使用同样频率进行通信·如何实现?——通过各用户的码序列进行区分。二、CDMA原理2.1表示1、每个比特(0或1)以一组码序列发送(m位编码将每位比特划分为m)码片:一个数据信号(如逻辑1或0)通常要用多个编码信号来进行编码,那么其中的一个编码信号就称为一个码片2、一个数据信号(如逻辑1或0)通常要用多个编码信号来进行编码,如这个站要发送1,就发送该码片的原码,如要发送0,就发送给码片的反码每个站都会分配一个码片序列,那么如何选择码片序列呢?

  10. go - 如何与 gorilla 复用器建立无状态连接? - 2

    我的程序在每次一个连接的情况下运行良好,但在并发连接的情况下就不行。我需要由一个函数呈现所有连接,该函数将包含我在服务中需要的所有数据,但效果不佳,因此我用下面的简单代码进行了说明:packagemainimport("encoding/json""fmt""github.com/gorilla/mux""github.com/rs/cors""net/http""reflect""time")varOutstruct{Codeint`json:"status"`Message[]interface{}`json:"message"`}funcClear(vinterface{}){p

随机推荐