golang channel 关闭之比优雅方案更优雅的方案

如果没了解过优雅方案,可以看一下这个简书如何优雅地关闭Go channel。

在我看来这些方案不优雅!不灵活!不实用!比如1个消费者N个生产者的情况下,如果要求消费者要把channel消费完该怎么处理?

要做到优雅,我觉得有3点:

  1. 消费者不关闭channel
  2. 消费者必须把channel内的数据消费完
  3. channel必须在没有生产者使用时被关闭

我们只要做好第3点,第1第2点唾手可得。

现在来看看更优雅的方案,解决问题的思路就是维护一个生产者的数量sendersNum,当sendersNum为0的时候关闭channel。

import (
   "math/rand"
   "sync"
   "time"
)

func main() {
   //  并发访问sendersNum需要锁
   l := &sync.Mutex{}
   //  生产者的数量
   sendersNum := 0
   //  data channel
   dataChan := make(chan int, 5)

   //  发起10个生产者
   for i := 0; i < 10; i++ {
      l.Lock()
      sendersNum++
      l.Unlock()
      go func() {
         for {
            x := rand.Intn(20)
            //  如果x<5, 生产者退出
            if x < 5 {
               l.Lock()
               sendersNum--
               l.Unlock()
               return
            } else {//  否则x>=5, 发送数据到dataChan
               dataChan <- x
            }

         }
      }()
   }

   //  发起10个消费者
   for i := 0; i < 10; i++ {
      go func() {
         //  消费者要做的事情就是不断读取直到ok字段为false
         for {
            x, ok := <- dataChan
            if !ok {
               return
            } else {
               println(x)
            }
         }
      }()
   }

   //  发起一个关闭者,是该方案的核心
   go func() {
      //  关闭者每隔1秒醒来检查sendersNum
      //  当sendersNum为0的时候,说明没有生产者再使用channel了,可以放心关闭
      //  使用time.Sleep是一个粗糙的实现,但并不妨碍我们说明问题,也可以使用更优雅的条件变量
      for  {
         time.Sleep(1*time.Second)
         l.Lock()
         if sendersNum == 0 {
            close(dataChan)
            return
         }
         l.Unlock()
      }
   }()

   //  避免主协程结束导致子协程提前结束,也是一个粗糙的实现
   time.Sleep(10*time.Second)
}

 这是多个消费者多个生产者的情况。如果只有一个发送者,可以将关闭者优化掉,在发送者退出时直接关闭channel。如果只有一个消费者,虽然也可以优化掉关闭者,但还是建议采用这个关闭者的方案。

这个方案是灵活的,唯一的要求就是维护sendersNum,发起生产者的时候sendersNum++,生产者退出的时候sendersNum-- 。至于生产者为什么会退出不做要求,可以是x不满足条件,也可以是消费者关闭了一个stopCh,或者是发生了超时事件。

当一个生产者需要向多个channel发送数据时,决定停止向某个channel发送数据,将该channel的sendersNum-- 就可以了,然后继续生产其他channel的数据了。

关闭者也可以同时检查多个channel的sendersNum。

这个方案也是实用的。有多少生产者会向channel发送数据是个重要的信息,sendersNum不仅可以用来关闭channel,还可以做为监控系统的一个指标。

在应用上还是以实用为主,优雅关闭方案只是提供了一种选择,defer recovery的方案也是一种选择,还有库中用得比较多的用closed字段来维护channel的方案。

最后,大四刚开学,实战经验少,轻喷。

你可能感兴趣的:(golang,channel)