基于channel的通信模型实践

本文详细介绍了Go语言中基于Channel的并发通信模型,包括多生产者单消费者(MPSC)和单生产者多消费者(SPMC)模型。通过实例代码展示了如何创建、发送、接收和关闭Channel,以及解决死锁问题的方法。在MPSC模型中,解释了共享channel和私有channel的两种实现方式,并讨论了消费者如何获取生产者的消息处理结果。而在SPMC模型中,阐述了生产者如何通过关闭channel通知消费者停止读取。
摘要由CSDN通过智能技术生成

前言

channel 作为 Go 核心的数据结构和 Goroutine 之间的通信方式,Channel 是支撑 Go 语言高性能并发编程模型的重要结构本节会介绍管道 Channel 的设计原理、数据结构和常见操作,例如 Channel 的创建、发送、接收和关闭。 ​

在进入主题内容之前,读者需要先掌握下表中的不同状态下的channel执行Read、Write、close操作所会产生的结果。
在这里插入图片描述
图来自 曹大 https://github.com/cch123 ​

Go 语言中最常见的、也是经常被人提及的设计模式就是:不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。虽然我们在 Go 语言中也能使用共享内存加互斥锁进行通信,但是 Go 语言提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。 本文将会介绍基于channel实现的多种通信模型 ​

MPSC:多生产者单消费者模型

对于MPSC的应用场景中,多个生产者负责生产数据,只有一个消费者来消费数据, 而这个模型又可分为两种实现方式:

1,多个生产者是公用一个channel 来和消费者通信
2,使用自己独有的channel来和消费者通信

变种一:生产者共用channel
在这里插入图片描述
如图所示,左边有多个生产goroutine往公共的channel中写入数据,右边只有一个消费goroutine从这个channel中读取数据进行处理。

基础版

我们首先定义传递的消息结构体定义:

type Msg struct {
   
    in int
}

然后实现生产者如下,其中参数 sendChan就是生产者和消费者进行通信的channel

// 生产者
func producer(sendChan chan Msg) {
   
    for i := 0; i < 10; i++ {
   
        sendChan <- Msg{
   in: i}
    }
}

消费者以及消息处理函数定义如下,其中参数 sendChan就是生产者和消费者进行通信的channel,当前消息处理函数process目前只是把消息内容打印出来。

// 消费者
func consumer(sendChan chan Msg) {
   
    for v := range sendChan {
   
        process(v)
    }
}

// 消息处理函数
func process(msg Msg){
   
    fmt.Println(msg)
}

mpsc的模型代码如下,首先创建通信用的channel,然后开启三个生产者goroutine,一个消费者goroutine。

func mpsc() {
   

    sendChan := make(chan Msg, 10)

    for p := 0; p < 3; p++ {
   
        go producer(sendChan)
    }

    go consumer(sendChan)
}

main函数如下, 里面 select{} 是为了保持main函数所在的goroutine一直阻塞,不然main函数立刻退出后,生产者和消费者goroutine说不定还没有执行或只执行了一部分就退出了。

func main() {
   
    mpsc()
    select{
   }
}

完整代码 在线演示 https://www.online-ide.com/IBPZGOdesk 结果如下

{
   0}
{
   1}
{
   2}
{
   3}
{
   4}
{
   5}
{
   6}
{
   7}
{
   8}
{
   9}
{
   0}
{
   1}
{
   2}
{
   3}
{
   4}
{
   5}
{
   6}
{
   7}
{
   8}
{
   9}
{
   0}
{
   1}
{
   2}
{
   3}
{
   4}
{
   5}
{
   6}
{
   7}
{
   8}
{
   9}
fatal error: 
all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
    /home/kingeasternsun/1ba7ba19-5e66-4f4c-beb3-cb5e3e6d881e/main.go:9 +0x25
goroutine 9 [chan receive]:
main.consumer(0xc000072000)
    /home/kingeasternsun/1ba7ba19-5e66-4f4c-beb3-cb5e3e6d881e/main.go:25 +0xa9
created by main.mpsc
    /home/kingeasternsun/1ba7ba19-5e66-4f4c-beb3-cb5e3e6d881e/main.go:43 +0x9b
exit status 2


** Process exited - Return Code: 1 **

可以看到打印的数字是由交叉的,说明多个生产者并发的执行了写入。 不过最后生产者发送完后程序有两个报错:

1,第一条 goroutine 1 [select (no cases)]: 是指 select{} 一直阻塞,
2,第二条 goroutine 9 [chan receive]: 是指在于生产者发送完数据库后没有新的数据写入channel,而消费者消费完channel中的数据库,再从空的channel中读数据就会一致阻塞,所以上面报fatal error: all goroutines are asleep - deadlock! 这个错误。

因为我们是demo,为了保持main活着使用了 select{} ,实际项目中mspc往往会在一个持续运行的程序中调用,所以就没有上面的问题了。 ​

当然我们也可以直接来修复这个错误,让生产者都发送完后给消费者发消息让其退出即可。

修复deadlock问题

要等生产者都发送完后才给消费者发消息,那么我们就需要使用sync.WaitGroup来进行同步。 生产者如何给消费者发消息,同时要保证消费者把之前的消息处理完后才退出。我们可以又两种方案:

1,发送一个特殊标记的Msg,标记这个消息是终止消息
2,close channel

第1种方案会造成通信额外的内存占用消耗,推荐第2种方案。 ​

首先修改生产者代码如下,入参多了一个 wg *sync.WaitGroup , 生产者发送完数据后调用wg.Done()

// 生产者
func producer(sendChan 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值