Go并发-使用协程、通道和select

栏目: Go · 发布时间: 5年前

内容简介:通道(channel)和协程(goroutine)是实现Go并发程序的两种机制。其中,通道又分为无缓冲通道和有缓冲通道两种,在编写实际的并发程序时,基本都使用异步模式的有缓冲通道。通道又可细分为支持读和写的双向通道,只读的通道,只写的通道三种。无论是有缓存通道、还是无缓冲通道都存在阻塞的情况。阻塞场景共4个,有缓存和无缓冲各2个。无缓冲通道的特点是,发送的数据需要被读取后发送才会完成(同步),它阻塞场景是:

通道(channel)和协程(goroutine)是实现 Go 并发程序的两种机制。其中,通道又分为无缓冲通道和有缓冲通道两种,在编写实际的并发程序时,基本都使用异步模式的有缓冲通道。通道又可细分为支持读和写的双向通道,只读的通道,只写的通道三种。

通道阻塞场景

无论是有缓存通道、还是无缓冲通道都存在阻塞的情况。阻塞场景共4个,有缓存和无缓冲各2个。

无缓冲通道的特点是,发送的数据需要被读取后发送才会完成(同步),它阻塞场景是:

  • 通道中无数据,但执行读通道。
  • 通道中无数据,向通道写数据,但无协程读取。

有缓存通道的特点是,有缓存时可以向通道中写入数据后直接返回(异步),它阻塞场景是:

  • 通道缓存无数据,但执行读通道(接收数据)。
  • 通道缓存已经占满,向通道写数据(发送数据),但无协程读。

使用协程、通道和select

Go的select关键字可以让我们操作多个通道,将协程(goroutine),通道(channel)和select结合起来构成了Go的一个强大特性。

首先,我们来看一个简短代码,如下所示

package main

import (
    "time"
    "fmt"
)

func main() {
    c1 := make(chan string, 1)         //定义两个有缓冲通道,容量为1
    c2 := make(chan string, 1)

    go func() {
        time.Sleep(time.Second * 1)   //每隔1秒发送数据
        c1 <- "name: xuchao"
    }()

    go func() {
        time.Sleep(time.Second * 2)    //每隔2秒发送数据
        c2 <- "age: 25"
    }()

    for i:=0; i<2; i++ {                //使用select来等待这两个通道的值,然后输出
        select {
        case msg1 := <- c1:
            fmt.Println(msg1)
        case msg2 := <- c2:
            fmt.Println(msg2)
        }
    }
}

执行结果

# go run channel.go 
name: xuchao
age: 25

如我们所期望的,程序输出了正确的值。对于select语句而言,它不断地检测通道是否有值过来,一旦发现有值过来,立刻获取输出。

使用Select+超时实现无阻塞读写

select是执行选择操作的一个结构,它里面有一组case语句,它会执行其中无阻塞的那一个,如果都阻塞了,那就等待其中一个不阻塞,进而继续执行,它有一个default语句,该语句是永远不会阻塞的,我们可以借助它实现无阻塞的操作。

但使用default实现的无阻塞通道阻塞有一个缺陷:当通道不可读或写的时候,会即可返回。实际场景更多的需求是,我们希望尝试读一会数据,或者尝试写一会数据,如果实在没法读写再返回,程序继续做其它的事情。

使用定时器替代default可以解决这个问题,给通道增加读写数据的容忍时间,如果500ms内无法读写,就即刻返回。示例代码修改一下会是这样:

func ReadWithSelect(ch chan int) (x int, err error) {
    timeout := time.NewTimer(time.Microsecond * 500)

    select {
    case x = <-ch:
        return x, nil
    case <-timeout.C:
        return 0, errors.New("read time out")
    }
}

func WriteChWithSelect(ch chan int) error {
    timeout := time.NewTimer(time.Microsecond * 500)

    select {
    case ch <- 1:
        return nil
    case <-timeout.C:
        return errors.New("write time out")
    }
}

结果就会变成超时返回:

read time out
write time out
read time out
write time out

总结

为什么Go语言对通道要限制长度而不提供无限长度的通道?

我们知道通道(channel)是在两个 goroutine间通信的桥梁。当数据提供方供给速度大于消费方数据处理速度时,如果通道不限制长度,那么内存将不断膨胀直到应用崩溃。因此,限制通道的长度有利于约束数据提供方的供给速度,才能正常地处理数据。

常用的select场景

  • 无阻塞的读、写通道。即使通道是带缓存的,也是存在阻塞的情况,使用select可以完美的解决阻塞读写。
  • 给某个请求/处理/操作,设置超时时间,一旦超时时间内无法完成,则停止处理。
  • select本色:多通道处理。

解决阻塞的2种办法

  • 使用select的default语句,在channel不可读写时,即可返回
  • 使用select+定时器,在超时时间内,channel不可读写,则返回(推荐方式)

记住,在for循环里不要使用select + time.After的组合,易造成内存泄漏,应当使用NewTimer来做定时器。当使用golang过程中,遇到性能和内存gc问题,都可以使用golang tool pprof来排查分析问题。

希望这篇文章对你开发Go并发程序有所启发。


以上所述就是小编给大家介绍的《Go并发-使用协程、通道和select》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法设计与应用

算法设计与应用

迈克尔 T. 古德里奇(Michael T. Goodrich)、罗伯特·塔马契亚(Roberto Tamas / 乔海燕、李悫炜、王烁程 / 机械工业出版社 / 2017-11-20 / CNY 139.00

本书全面系统地介绍算法设计和算法应用的各个领域,内容涵盖经典数据结构、经典算法、算法分析方法、算法设计方法以及算法在各个领域的应用,还包含一些高级主题。本书采用应用驱动的方法引入各章内容,内容编排清晰合理,讲解由浅入深。此外,各章都附有巩固练习、创新练习和应用练习三种类型的题目,为读者理解和掌握算法设计和应用提供了很好的素材。 本书可作为高等院校计算机及相关专业“数据结构和算法”课程的本科生......一起来看看 《算法设计与应用》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具