Go语言channel阻塞关闭实验

Go by 达达 at 2012-05-09

在游戏中有一个常见的场景:玩家A的进程向玩家B的进程发送消息,以完成一次互动

使用Go语言实现上面这个场景,有可能出现一种异常情况:玩家A像玩家B的一个特定channel发送消息,并阻塞等待玩家B接收,但是玩家B进程正在执行一次tcp发送,这时候tcp发送失败,玩家B被系统判定成掉线,进而玩家B进程退出了,这时候玩家B的channel也将随之关闭

这里就会有一个疑问,玩家A正在等待channel被接收,但是玩家B把这个channel关闭了,将会发生什么呢?

这里有一个简化以上模型的实验代码:

package main

import "fmt"

func main() {
  a := make(chan int)
  b := make(chan int)
  
  go func(a, b chan int) {
    fmt.Println("A wait")
    a <- 1
    fmt.Println("A exit")
    b <- 1
  }(a, b)
  
  go func(a chan int) {
    fmt.Println("B exit")
    close(a)
  }(a)
  
  <- b
  
  fmt.Println("C exit")
}

以上实验代码模拟两个Go进程,进程A堵塞在发送消息,B进程启动后就把channel关闭。

运行以上代码将得到以下结果:

点击这里在线执行

A wait
B exit
panic: runtime error: send on closed channel

goroutine 2 [running]:
main._func_001(0xf840001910, 0xf8400018c0, 0x0, 0x0)
    /tmpfs/gosandbox-4b95d6cd_c5d5dd69_106d64b3_e851ad9c_c4476823/prog.go:11 +0xaa
created by main.main
    /tmpfs/gosandbox-4b95d6cd_c5d5dd69_106d64b3_e851ad9c_c4476823/prog.go:14 +0x74

goroutine 1 [chan receive]:
main.main()
    /tmpfs/gosandbox-4b95d6cd_c5d5dd69_106d64b3_e851ad9c_c4476823/prog.go:21 +0xa4

上面的实验说明channel被关闭将导致发送端异常。那么如何满足最前面我们提出的需求呢?

下面是改进后的代码:


package main

import "fmt"

func main() {
  a := make(chan int)
  b := make(chan int)
  c := make(chan int)
  
  go func(a, b, c chan int) {
    fmt.Println("A wait")
    select {
      case b <- 1:
        fmt.Println("This will never happen")
      case <- a:
        fmt.Println("A knows B exit")
    }
    fmt.Println("A exit")
    c <- 1
  }(a, b, c)
  
  go func(a, b chan int) {
    fmt.Println("B exit")
    close(b)
    a <- 1
  }(a, b)
  
  <- c
  
  fmt.Println("C exit")
}

第二段实验代码中,加入了另外一个channel,用以在B进程退出时发送退出通知,这时候再运行代码将得到以下结果。

点击这里在线执行

A wait
B exit
A knows B exit
A exit
C exit

看起来好像已经可以满足需求了?但是还有个问题,B进程发送退出通知到channel里的时候,只能由一个进程先接收到,如果此时是多个进程都在等待B进程接收消息,该如何处理呢?留个疑问大家自己思考吧 :)