【青训营】浅谈channel(二)

66 阅读1分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记。

waitGroup

由于goroutine是并发的,所以channel想统一管理非常的困难,这就需要用到青训营第二节课中提到的waitGroup同步机制,示例代码如下:

package main

import (
   "fmt"
   "sync"
)

func doWork(id int,
   w worker) {
   for n := range w.in {
      fmt.Printf("Worker %d received %c\n",
         id, n)
      w.done()
   }
}

type worker struct {
   in   chan int
   done func()
}

func createWorker(
   id int, wg *sync.WaitGroup) worker {
   w := worker{
      in: make(chan int),
      done: func() {
         wg.Done()
      },
   }
   go doWork(id, w)
   return w
}

func chanDemo() {
   var wg sync.WaitGroup

   var workers [10]worker
   for i := 0; i < 10; i++ {
      workers[i] = createWorker(i, &wg)
   }

   wg.Add(20)
   for i, worker := range workers {
      worker.in <- 'a' + i
   }
   for i, worker := range workers {
      worker.in <- 'A' + i
   }

   wg.Wait()
}

func main() {
   chanDemo()
}

waitGroup有著名的三大方法,分别是Add方法、Wait方法和Done方法,可以有效解决同步问题。

在创建workers即接收数据的channel时,每次都要将waitGroup以指针形式传入,因为全局只能有一个waitGroup,否则同步机制会混乱。

在worker结构体中,in是需要接收的数据,然后为了好看又把wg.Done()封装进了结构体中。这是一个好的编程思想,此处是画蛇添足,但以后就可以在实现的时候隐去实现细节,专注编写新的代码。

w.done()方法就是通知waitGroup这个通道的数据已经全部接收完毕了。

这样就不用再去增添一个参数 done chan bool,然后done<-true,最后再在主函数中接收<-worker.done了。

Add方法是说明当前有多少个任务,此处有20个任务。

wg.Wait()方法就是阻塞主线程,直到所有的协程都已经完成了接受数据和打印,然后再结束。

Select方法

package main

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

func generator() chan int {
   out := make(chan int)
   go func() {
      i := 0
      for {
         time.Sleep(
            time.Duration(rand.Intn(1500)) *
               time.Millisecond)
         out <- i
         i++
      }
   }()
   return out
}

func worker(id int, c chan int) {
   for n := range c {
      time.Sleep(time.Second)
      fmt.Printf("Worker %d received %d\n",
         id, n)
   }
}

func createWorker(id int) chan<- int {
   c := make(chan int)
   go worker(id, c)
   return c
}

func main() {
   var c1, c2 = generator(), generator()
   var worker = createWorker(0)

   var values []int
   tm := time.After(10 * time.Second)
   tick := time.Tick(time.Second)
   for {
      var activeWorker chan<- int
      var activeValue int
      if len(values) > 0 {
         activeWorker = worker
         activeValue = values[0]
      }

      select {
      case n := <-c1:
         values = append(values, n)
      case n := <-c2:
         values = append(values, n)
      case activeWorker <- activeValue:
         values = values[1:]

      case <-time.After(800 * time.Millisecond):
         fmt.Println("timeout")
      case <-tick:
         fmt.Println(
            "queue len =", len(values))
      case <-tm:
         fmt.Println("bye")
         return
      }
   }
}

select方法可以在所有goroutine协程中选择最快的那个去执行。下面就是一个select方法的实例,其中有到一定时间接收不到数据的timeout机制,以及时间戳定时打印机制,以及定时退出机制,还有Worker的初始化以及从c1,c2两个channel中收集数据的机制,在此作为示范,不再赘述。

今天的笔记就分享到这里,有什么问题可以在评论区交流讨论