如何正确的关闭go channel

732 阅读1分钟

go channel在dave cheney的blog中讲过,能不用就不用,实际上的确,在你没法100%把控它的时候,使用的同时也就埋下了一个炸弹。但是作为go的关键特性之一,不用也略微浪费。

主要的其实就几点

  • 不要close closed channel
  • 不要给closed channel发消息

正确关闭示例,利用信号的方式

  • 示例1
func main() {
	rand.Seed(time.Now().UnixNano())

	const Max = 100000
	const NumSenders = 1000

	dataCh := make(chan int, 100)
	stopCh := make(chan struct{})

	for i := 0; i < NumSenders; i++ {
		go func() {
			for {
				select {
				// for select不堵塞的等待关闭信号
				case <-stopCh:
					return
				case dataCh <- rand.Intn(Max):
				}
			}
		}()
	}

	go func() {
		for value := range dataCh {
			// 发送关闭信号
			if value == Max-1 {
				close(stopCh)
				return
			}
		}
	}()

	// 超时结束main goroutine
	select {
	case <-time.After(time.Hour):
	}
}

缺点

  • 示例2
func main() {
    rand.Seed(time.Now().UnixNano())

    const Max = 100000
    const NumReceivers = 10
    const NumSenders = 1000

    dataCh := make(chan int, 100)
    stopCh := make(chan struct{})

    // It must be a buffered channel.
    toStop := make(chan string, 1)

    var stoppedBy string

    // moderator
    go func() {
        stoppedBy = <-toStop
        close(stopCh)
    }()

    // senders
    for i := 0; i < NumSenders; i++ {
        go func(id string) {
            for {
                value := rand.Intn(Max)
                if value == 0 {
                    select {
                    case toStop <- "sender#" + id:
                    default:
                    }
                    return
                }

                select {
                case <- stopCh:
                    return
                case dataCh <- value:
                }
            }
        }(strconv.Itoa(i))
    }

    // receivers
    for i := 0; i < NumReceivers; i++ {
        go func(id string) {
            for {
                select {
                case <- stopCh:
                    return
                case value := <-dataCh:
                    if value == Max-1 {
                        select {
                        case toStop <- "receiver#" + id:
                        default:
                        }
                        return
                    }

                    fmt.Println(value)
                }
            }
        }(strconv.Itoa(i))
    }

    select {
    case <- time.After(time.Hour):
    }

}

第二个例子就是利用了一个中间人的角色,这里设置成缓冲的toStop原因就是避免丢失关闭的信息,简单实验倒是没什么太大影响。

原则

  • don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders.

  • don't close (or send values to) closed channels.

go101.org/article/cha…