请选择 进入手机版 | 继续访问电脑版

技术控

    今日:0| 主题:61300
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Passing channels over channels

[复制链接]
懂我与否 发表于 2016-10-11 12:48:46
97 4
Passing Channels over Channels

  As most know,    channelsare one of the most powerful concurrency features in Go. Armed with Goroutines and the    selectstatement, you can build correct, efficient and understandable concurrent programs that do complex things.  
  In essence, a channel is a shared, concurrency-safe queue. Its primary purpose is to pass data across concurrency boundaries (i.e. between goroutines). Another way to say that is: you can send or receive an instance of any    typeon a channel. I’m going to focus on sending that    chantype over a channel.  
  Why

  One simple reason you’d send a    chanon a    chanis to tell a goroutine to do work and then get an acknowledgement (ack hereafter) that it’s finished doing that work.  
  Here’s what such a channel looks like in Go code:
  1. chanOverChan := make(chan chan int)
复制代码
In English, this code means: “a channel on which you can send or receive a channel of ints”. When you see code that looks like the above, it’s a safe bet that the sender is telling the receiver to do some computation and send the results to another goroutine, which may be the sender. We’re going to focus on case where the sender is the receiver that the ack is forwarded to.
  Patterns

  You won’t always see a simple    chan chan int. Sometimes, the ack channel is stored inside a struct:  
  1. type data struct {
  2.   retCh chan<- int
  3. }
  4. dataCh := make(chan data)
复制代码
And you might see the channel completely abstracted by a    func:  
  1. type abstractedCh := chan func(int)
复制代码
In this case, the sender can capture the channel inside the    func(int)if they want – or they can send any other implementation they want. This strategy is called a    function closure, and is extremely flexible.  
  In Action

  Below are some code examples using the 3 strategies. In each case, We’ll simulate the work using a simple    time.Sleep.  
  Style 1: Using a Channel Inside a Channel

  Here’s the simplest of the patterns in action. Generally this style will be easiest to read and understand, but it has some limits:
  
       
  • Each      doStuffgoroutine sleeps for a set amount of time. You can’t change the sleep time when you send on      ch   
  • Each      doStuffgoroutine can      onlyreceive a      chan time.Duration– no more data than that. We’ll address that problem in the next style.  
  1. package main
  2. import (
  3.         "log"
  4.         "sync"
  5.         "time"
  6. )
  7. // the function to be run inside a goroutine. It receives a channel on ch, sleeps for t, then sends t on the channel it received
  8. func doStuff(t time.Duration, ch <-chan chan time.Duration) {
  9.         ac := <-ch
  10.         time.Sleep(t)
  11.         ac <- t
  12. }
  13. func main() {
  14.         // create the channel-over-channel type
  15.         sendCh := make(chan chan time.Duration)
  16.         // start up 10 doStuff goroutines
  17.         for i := 0; i < 10; i++ {
  18.                 go doStuff(time.Duration(i+1)*time.Second, sendCh)
  19.         }
  20.         // send channels to each doStuff goroutine. doStuff will "ack" by sending its sleep time back
  21.         recvCh := make(chan time.Duration)
  22.         for i := 0; i < 10; i++ {
  23.                 sendCh <- recvCh
  24.         }
  25.         // receive on each channel we previously sent. this is where we receive the ack that doStuff sent back above
  26.         var wg sync.WaitGroup // use this to block until all goroutines have received the ack and logged
  27.         for i := 0; i < 10; i++ {
  28.                 wg.Add(1)
  29.                 go func() {
  30.                         defer wg.Done()
  31.                         dur := <-recvCh
  32.                         log.Printf("slept for %s", dur)
  33.                 }()
  34.         }
  35.         wg.Wait()
  36. }
复制代码
See this code in action at    https://play.golang.org/p/-1lY-4gd4N.  
  Style 2: Using a Channel Stored Inside a Struct

  This code will look almost identical to the previous snippet, with 2 exceptions:
  
       
  • The ack channel will be stored inside a      struct   
  • The sleep time will be stored inside that same      struct, so we can pass it over the      channel
              
    • This makes the code more flexible, because we can tell          doStuffhow long to sleep when we          sendto it, rather than when we start it      
          
  1. package main
  2. import (
  3.         "log"
  4.         "sync"
  5.         "time"
  6. )
  7. // the struct that we'll pass over a channel to a goroutine running doStuff
  8. type process struct {
  9.         dur time.Duration
  10.         ch  chan time.Duration
  11. }
  12. // the goroutine function. will receive a process struct 'p' on ch, sleep for p.dur, then send p.dur on p.ch
  13. func doStuff(ch <-chan process) {
  14.         proc := <-ch
  15.         time.Sleep(proc.dur)
  16.         proc.ch <- proc.dur
  17. }
  18. func main() {
  19.         // start up the goroutines
  20.         sendCh := make(chan process)
  21.         for i := 0; i < 10; i++ {
  22.                 go doStuff(sendCh)
  23.         }
  24.         // store an array of each struct we sent to the goroutines
  25.         processes := make([]process, 10)
  26.         for i := 0; i < 10; i++ {
  27.                 dur := time.Duration(i+1) * time.Second
  28.                 proc := process{dur: dur, ch: make(chan time.Duration)}
  29.                 processes[i] = proc
  30.                 sendCh <- proc
  31.         }
  32.         // recieve on each struct's ack channel
  33.         var wg sync.WaitGroup // use this to block until all goroutines have received the ack and logged
  34.         for i := 0; i < 10; i++ {
  35.                 wg.Add(1)
  36.                 go func(ch <-chan time.Duration) {
  37.                         defer wg.Done()
  38.                         dur := <-ch
  39.                         log.Printf("slept for %s", dur)
  40.                 }(processes[i].ch)
  41.         }
  42.         wg.Wait()
  43. }
复制代码
See this code in action at    https://play.golang.org/p/bJoiGP9ua2.  
  Style 3: Using a Channel Inside a Function Closure

  This code will look different from the previous examples, because the    doStufffunction won’t know    anythingabout a return channel. That fact is both good and bad. On the up side, you can change your code later to do anything you want inside that function (e.g. good for testing!), but on the down side, you can’t pass dynamic    time.Durations into the    doStuffgoroutines, as you could in the previous example.  
  1. package main
  2. import (
  3.         "log"
  4.         "sync"
  5.         "time"
  6. )
  7. func doStuff(dur time.Duration, ch <-chan func(time.Duration)) {
  8.         ackFn := <-ch
  9.         time.Sleep(dur)
  10.         ackFn(dur)
  11. }
  12. func main() {
  13.         // start up the doStuff goroutines
  14.         sendCh := make(chan func(time.Duration))
  15.         for i := 0; i < 10; i++ {
  16.                 dur := time.Duration(i+1) * time.Second
  17.                 go doStuff(dur, sendCh)
  18.         }
  19.         // create the channels that will be closed over, create functions that close over each channel, then send them to the doStuff goroutines
  20.         recvChs := make([]chan time.Duration, 10)
  21.         for i := 0; i < 10; i++ {
  22.                 recvCh := make(chan time.Duration)
  23.                 recvChs[i] = recvCh
  24.                 fn := func(dur time.Duration) {
  25.                         recvCh <- dur
  26.                 }
  27.                 sendCh <- fn
  28.         }
  29.         // receive on the closed-over functions
  30.         var wg sync.WaitGroup // use this to block until all goroutines have received the ack and logged
  31.         for _, recvCh := range recvChs {
  32.                 wg.Add(1)
  33.                 go func(recvCh <-chan time.Duration) {
  34.                         defer wg.Done()
  35.                         dur := <-recvCh
  36.                         log.Printf("slept for %s", dur)
  37.                 }(recvCh)
  38.         }
  39.         wg.Wait()
  40. }
复制代码
See this code in action at    https://play.golang.org/p/JAtGxdBVRW.  
  Summary

  There are uses for this channel-over-channel strategy, but the ack one is simple and powerful. Further, in many cases when you need to “return” something to another goroutine, sending it a    chanon which it can return a value is often the easiest way to do it. This pattern can even be useful when you want to wait for a goroutine to ack its completion. Note, however, that you can also do ack-ing with a    sync.WaitGroup.
qtkh2035 发表于 2016-10-11 15:18:30
在几百万帖里找到你,很不容易
回复 支持 反对

使用道具 举报

baby996 发表于 2016-10-15 03:02:27
嘘,低调。
回复 支持 反对

使用道具 举报

慕灵 发表于 2016-10-19 11:07:24
朋友妻不可欺睡睡觉还可以
回复 支持 反对

使用道具 举报

姜礼超 发表于 2016-10-20 13:41:34
高手云集 果断围观
回复 支持 反对

使用道具 举报

我要投稿

回页顶回复上一篇下一篇回列表
手机版/c.CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 | 粤公网安备 44010402000842号 )

© 2001-2017 Comsenz Inc.

返回顶部 返回列表