Golang 空通道考究

一、简介

最近看到了一些帖子和博客在讨论golang里面对空通道 1 进行读写操作。本人也比较好奇,就实验了一下,发现和大家的描述都有出入,只好再看源码分析。

二、结论要点

空通道 读写操作会有以下结果:

  1. 形如 <- chanchan<-,这种形式的读写会直接阻塞所在协程,并且无法解除阻塞
  2. 形如 select {case _ <- chan},这种形式的读操作会直接导致程序崩溃2

三、操作与分析

1、简单源码实验

验证读写休眠:

func main() {
	var c chan struct{}
	go func() {
		fmt.Println("开始接收")
		<-c
		fmt.Println("接收完成")
	}()
	go func() {
		fmt.Println("开始发送")
		c <- struct{}{}
		fmt.Println("发送完成")
	}()
	time.Sleep(5 * time.Second)
}

输出:

开始接收
开始发送

现象:观察到输出上面两行以后会停滞5秒,然后程序退出。

分析可知,当前条件下:

  1. 输出 开始接收开始发送),没有输出 接收完成发送完成),可以知道:对空通道读写确实会发生阻塞
  2. 两个协程一个读、另一个写,都发生了阻塞,可以说明:空通道读写是无效操作,并不会真正的发送、接收。3

2、源码分析

既然没人能说明究竟是怎么回事,那就需要翻源码了。写这篇文章时,我阅读的是 1.17.6 版本的golang源码。

2.1 通道结构

通道相关的源码在 go/src/runtime/chan.go 中,chan 的本体是结构体 type hchan struct{...}

2.2 发送接口

通道的发送接口有3个调用链条:

  1. func chansend1(c *hchan, elem unsafe.Pointer) -> func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)
  2. func selectnbsend(c *hchan, elem unsafe.Pointer) -> func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)
  3. func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) -> func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr)

其中 chansend 是最底层的发送接口:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
	if c == nil { // channel 为nil时:
		if !block { // 1. 当前以非阻塞模式发送:
			return false // 直接返回false,表示通道已关闭或不可用
		}
      // 2. 当前为阻塞模式:
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2) // 2.1 协程进入休眠状态
		throw("unreachable") // panic
	}
    ... ...
}

观察代码和注释,可以知道:

  1. 第一个链条 chansend1 对应形如 <- chan 这种格式的操作。

    这种情况下 chansend 的参数 block 始终为 true

    func chansend1(c *hchan, elem unsafe.Pointer) {
        chansend(c, elem, true, getcallerpc())
    }
    

    因此只要通道是 nil 就会导致当前协程阻塞,并且不能解除阻塞,因为通道不能再次关闭,也无法发送数据。

  2. 第二个链条 selectnbsend 对应形如 select {case _ <- chan} 这种格式的操作

    这种情况下 chansend 的参数 block 始终为 false

    func selectnbsend(c *hchan, elem unsafe.Pointer) (selected bool) {
        return chansend(c, elem, false, getcallerpc())
    }
    

    因此通道用 select 读数据会直接返回 false

  3. 第三个链条 reflect_chansend 对应函数 reflect.chansend 的操作

    这种情况下 chansend 的参数 block 由外部输入,因此需要根据 reflect 具体情况判断。

参考资料及附录


本文由 qingchuwudi 译制或原创,除非另有声明,在不与原著版权冲突的前提下,本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

知识共享许可协议


  1. 空通道是指 channel 的值 为 nil,通常是 channel 类型的变量没有初始化、已有的 channel 关闭形成的。 ↩︎

  2. panic 导致的程序崩溃 ↩︎

  3. 第2个结论表明 《Go题库·13》向为nil的channel发送数据会怎么样 里面的观点 “空通道即无缓冲通道。无缓冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作,这时值传送完成,两个goroutine都可以继续执行。” 是错误的。 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang 短信通道是使用 Go 语言开发的一种用于发送短信的通信工具。在现代社会中,短信通道是一种非常重要和常用的通信方式,它不仅可以用于个人之间的短信互通,还用于企业之间或企业与用户之间的信息传递。 使用 Golang 进行短信通道的开发具有以下几个优点: 1. 快速高效:Go 语言是一种编译型语言,具有非常高的执行效率和处理速度。在短信通道中,高效的执行速度和处理能力可以确保短信的及时发送和接收,保证通信的实时性。 2. 并发支持:Go 语言内置并发机制,使用轻量级的 goroutine 和通道(channel) 可以很方便地实现并发操作。在短信通道中,这种并发支持可以同时处理多个短信请求,提高短信的发送速度和通道的处理能力。 3. 代码简洁易读:Go 语言注重代码的简洁性和可读性,在语法设计上追求简单清晰。使用 Go 语言进行短信通道的开发,代码量相对较少,易于维护和理解。 4. 跨平台兼容:Go 语言的编译器能够生成可执行文件,使得开发的应用程序可以在不同的操作系统平台上运行。这样,使用 Golang 开发的短信通道可以在多个平台上使用,提高了通信的灵活性和可用性。 总的来说,Golang 短信通道是一种高效、可靠、并发支持的通信工具,在各种应用场景中都能展现出良好的性能和稳定性。它不仅可以用于个人之间的短信交流,还广泛应用于企业之间的业务通信和各种机构与用户之间的信息传递中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值