【问题标题】:Check if I can read from channel检查我是否可以从频道读取
【发布时间】:2021-01-21 15:30:03
【问题描述】:
package main

import (
    "fmt"
    "strconv"
    "time"
)

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

func printer(delay int, square bool, ch chan int) {
    for n := range ch {
        if (square) {
            fmt.Printf("["+strconv.Itoa(n) + "],")
        } else {
            fmt.Printf("("+strconv.Itoa(n) + "),")
        }
        time.Sleep(time.Duration(delay) * time.Millisecond)
    }
}

func sendToBoth(ch chan int) (ch1 chan int, ch2 chan int) {
    ch1 = make(chan int)
    ch2 = make(chan int)
    go func() {
        for {
            //n := <- ch
            select {
                case v := <- ch: //this is the problem point
                    ch1  <- v    //
                case v := <- ch: //
                    ch2 <- v     //
            }
        }
    }()
    return ch1, ch2
}

func main() {
    ch1, ch2 := sendToBoth(generator())
    go printer(100, true, ch1) //[]
    go printer(200, false, ch2) //()
    var name string
    fmt.Scan(&name)
}

我想实现 sendToBoth 函数,它从通道 ch 获取生成的数字 1,2,3,... 并将其发送到 ch1 和 ch2。但是每个都有不同的延迟,我不希望一个等待另一个解锁,所以我尝试使用select,但无法弄清楚如何询问如果ch1 或ch2 可以在案例条款的时刻。有什么帮助吗? 输出应该是这样的

(1),[1],[2],(2),[3],[4],(3),[5],[6],(4),[7],[8],...

【问题讨论】:

    标签: go concurrency channel goroutine


    【解决方案1】:

    所以我会说我的第一反应是“只要让他们一步步运行,就容易多了。”

    func sendToBoth(ch chan int) (ch1, ch2 chan int) {
        ch1 = make(chan int)
        ch2 = make(chan int)
        go func() {
            defer close(ch1)
            defer close(ch2)
            for n := range ch {
                ch1 <- n
                ch2 <- n
            }
        }()
        return ch1, ch2
    }
    

    如此简单,我喜欢它!但是,假设您希望 ch1 和 ch2 以各自的速率被消耗。如果您希望它们彼此分开,您必须使用临时存储来处理它。 简单的方法是给通道一些缓冲空间:

    ch1 = make(chan int, 10)
    ch2 = make(chan int, 10)
    

    现在,ch1 可以走得更快——但它只能比 ch2 领先 10 个项目。反之亦然。

    如果你想要一个无限大小的缓冲区,你必须自己保留它。我们可以利用nil 通道完全可以用作select 分支这一事实:

    func sendToBoth(ch chan int) (ch1, ch2 chan int) {
        ch1 = make(chan int)
        ch2 = make(chan int)
        go func() {
            defer close(ch1)
            defer close(ch2)
            var arr []int
            var pos1, pos2 int
            ich := ch
            for inch != nil && (pos1 < len(arr) || pos2 < len(arr)) {
                var och1, och2 chan int
                var v1, v2 int
                if pos1 < len(arr) {
                    och1 = ch1
                    v1 = arr[pos1]
                }
                if pos2 < len(arr) {
                    och2 = ch2
                    v2 = arr[pos2]
                }
                select {
                case n, ok := <- ich:
                    if !ok {
                        ich = nil // done
                    } else {
                        arr = append(arr, n)
                    }
                case och1 <- v1:
                    pos1++
                case och2 <- v2:
                    pos2++
                }
            }
        }()
        return ch1, ch2
    }
    

    有点复杂——我不完全确定这是正确的。另请注意,不会为流中的旧项目释放存储空间。

    【讨论】:

      【解决方案2】:

      如果来自ch1 和ch2 的值以不同的速率被消耗,并且您希望分配给两者而不必等待另一个,那么sendToBoth() 必须缓冲这些值直到它们被接收。在您的示例中,此缓冲区将不断增长。你想怎么处理?

      实现这种缓冲的一种非常简单合理的方法是创建、使用和返回缓冲通道:

      func sendToBoth(ch chan int) (ch1 chan int, ch2 chan int) {
          ch1 = make(chan int, 100)
          ch2 = make(chan int, 100)
          go func() {
              for n := range ch {
                  ch1 <- n
                  ch2 <- n
              }
          }()
          return ch1, ch2
      }
      

      仅此而已。只要缓冲区未满,在ch1 和ch2 上发送将是非阻塞的。当然,如果ch1 或ch2 的缓冲区之一已满,则在其上进一步发送将不得不等到接收到其中的一个元素,但这必须是可接受的折衷方案,否则没有限制缓冲元素的增长。你可以使用任何你想要的缓冲区大小。

      通过此更改,输出将如您所愿:

      [1],(1),[2],(2),[3],[4],(3),[5],[6],(4),[7],[8],(5),[9],[10],(6),[11],[12],(7),[13],[14],(8),[15],[16],(9),[17],[18],
      

      查看相关问题:How to broadcast message using channel

      【讨论】:

        猜你喜欢
        • 2018-07-28
        • 2013-06-23
        • 2019-03-18
        • 1970-01-01
        • 2010-11-03
        • 1970-01-01
        • 2021-04-13
        • 2011-03-24
        相关资源
        最近更新 更多