golang channel 边界情况那些事儿

1,158 阅读2分钟

概述

golang channel 是我们常用的一个用于处理协程通信的最重要的工具, 在一些边界情况下的表现,差异化很大。

本文就golang在channel正常状态、关闭状态、nil 状态下的发送接收以及关闭的表现做一个简单的测试说明, 有任何疑问欢迎交流探讨。

channel在各种状态下的操作表现结论

通道操作正常状态关闭状态nil
发送X (panic: send on closed channel)X
接收√ (接收到的为初始值)X
关闭X (panic: close of closed channel)X

正常发送和接收

示例代码

package main

import (
	"fmt"
)
func main() {
	ch := make(chan string)
	go send(ch)
	recv(ch)
	close(ch)       // 关闭
}
// 接收
func recv(ch <-chan string) {
	x := <-ch
	fmt.Println("result:", x)
}
// 发送
func send(ch chan<- string) {
	ch <- "send_val"
}

运行结果: (一切表现正常)

result: send_val

关闭状态的channel进行操作

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)
	close(ch)   // ok
	close(ch)   // panic: close of closed channel
	go send(ch) // panic: send on closed channel
	recv(ch)    // result:
	time.Sleep(time.Second)
}

func send(ch chan<- string) {
	ch <- "send_val"
}

func recv(ch <-chan string) {
	x := <-ch
	fmt.Println("result:", x)
}

nil channel的操作表现

  • 发送的时候: 情况稍复杂, 不存在接收者的时候, 则阻塞, 存在接受者的时候则直接报错 fatal error: all goroutines are asleep - deadlock!
  • 关闭的时候: 异常 panic: close of nil channel
  • 接收的时候: 有接收者存在的时候错误 fatal error: all goroutines are asleep - deadlock!, 无接收者存在的时候则block。

这些都是表象,究其根本原因还在于golang channel的底层实现,channel在发送或者接收的时候并不会直接检查channel 是否可用,而是先看看存在不存在对应会调度接受或者发送的go routine, 如果不存在,则挂起阻塞, 存在之后,才会通过这个 nil 的channel进行发送或者接收, 发送/接收的时候会发现channel非法报 goroutine 1 [chan receive (nil chan)]: 然后另外一端则继续进入阻塞状态

package main

import (
	"fmt"
	"time"
)

func main() {
	var ch chan string
	close(ch)   // panic: close of nil channel
	go recv(ch) //   goroutine 1 [chan receive (nil chan)]:
	send(ch)    // fatal error: all goroutines are asleep - deadlock!
	time.Sleep(time.Second)
}

func send(ch chan<- string) {
	ch <- "send_val"
}

func recv(ch <-chan string) {
	x := <-ch
	fmt.Println("result:", x)
}