项目开发中,使用golang的channel进行线程内的消息传递,由于使用了多个channel,所以使用select case对通道进行消息监听,处理最先发生变化的channel,但是出现了一直监听不到的情况,程序总是执行到select 中的default处理块。

下面是示例代码:

import "fmt"

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

    go func() { // 开启一个协程运行该函数
        select {
        case msg := <-ch1: // 从通道ch1中接收数据,并将数据赋值给msg
            fmt.Println("received msg", msg)
        default:
            fmt.Println("no msg received")
        }
    }()

    // 主线程
    msg := "hi"
    select {
    case ch1 <- msg: // 发送值msg到通道ch1中
        fmt.Println("sent msg", msg)
    default:
        fmt.Println("no msg sent")
    }

}

  

预期的输出结果是:

received msg

sent msg

 

而实际结果是:

no msg received

no msg sent

 

 

 

原因分析


我们设置的是无缓存的通道ch1(没有在声明时设置通道容量),这种通道有个特点,就是只有sender和receiver都准备好了后它们的通讯(communication)才会发生(Blocking),无缓存的channel只有在receiver准备好后send才被执行。

从上述特点来看,怀疑是协程中的receiver还没准备好,导致主线程中的sender无法被case,因而输出了no msg sent。

PS:当 select 中的其他条件分支都没有准备好的时候,default 分支会被执行。

 

换句话说,就是协程和主线程同时运行,但是协程代码的执行速度低于主线程的代码导致。

测试方式:往主线程加个延时器

import "fmt"
import "time"

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

    go func() { // 开启一个协程运行该函数
        select {
        case msg := <-ch1: // 从通道ch1中接收数据,并将数据赋值给msg
            fmt.Println("received msg", msg)
        default:
            fmt.Println("no msg received")
        }
    }()

    // 加入延时器 
    time.Sleep(100 * time.Millisecond)

    // 主线程
    msg := "hi"
    select {
    case ch1 <- msg: // 发送值msg到通道ch1中
        fmt.Println("sent msg", msg)
    default:
        fmt.Println("no msg sent")
    }

}

  

 

打印结果符合预期:

received msg

sent msg

 

上述结果确认了猜测,协程的执行速度低于主线程,因此针对协程开启的效率进行相关资料的查阅,确认了:

go新建一个goroutine需要一点时间,主线程走到select块的时候,接收消息的的goroutine一般都还没运行起来。

 

 

 

 

解决方案


根据实际情况,设置有缓存通道类型接口。

eg:

make(chan string, 100)

  

 

 

posted on 2021-08-25 19:07  Boom__Clap  阅读(503)  评论(0编辑  收藏  举报