技术控

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

[其他] Passing channels over channels

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

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
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.
友荐云推荐




上一篇:从0开始学习 GitHub 系列之「GitHub 常见的几种操作」
下一篇:Jenkins+Gradle实现Android自动化构建
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

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
高手云集 果断围观
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表