如何"尝试发送"到频道,如果频道已满,则中止?

时间:2014-01-06 04:47:47

标签: go

我有一个经典的“生产者 - 消费者”问题的变体。在我的计划中,有10个生产者并行工作,他们的目标是总共生产N个产品。

我考虑过使用缓冲通道:

products := make([]int, 100) // In total, produce 100 products

// The producers
for i := 0; i < 10; i++ {
    go func() {
        products <- 1 // !!
    }()
}

然而,它不起作用:

  • goroutine没有意识到达到了目标,通道发送了阻止,并且函数永远不会返回。
  • if len(products) < 100 { products <- 1 }不是原子操作,因此无效。

还有其他方法吗?

4 个答案:

答案 0 :(得分:3)

products := make([]int, 100)制作切片,而不是陈。你想要:

products := make(chan int, 100)

如果您真的想要进行非阻止发送,可以使用select:

select {
case products <- 1:
default:
}

这将首先尝试发送产品,如果是完整运行默认代码(no-op)并继续。

答案 1 :(得分:1)

执行此操作的最佳方法可能是在生产者中使用退出通道和select语句。 Go保证封闭通道始终注册为读取而不会阻塞。

Here's a working version on the playground

package main

import "fmt"
import "time"

func main() {
    productionChan := make(chan int)
    quit := make(chan struct{})
    for i := 0; i < 110; i++ {
        go produce(productionChan, quit)
    }

    consume(productionChan, quit)
    time.Sleep(5 * time.Second) // Just so we can observe the excess production channels quitting correctly
}

func consume(productionChan <-chan int, quit chan<- struct{}) {
    payload := make([]int, 100)

    for i := range payload {
        payload[i] = <-productionChan
    }

    close(quit)
    fmt.Println("Complete payload received, length of payload slice: ", len(payload))
}

func produce(productionChan chan<- int, quit <-chan struct{}) {
    select {
    case <-quit:
        fmt.Println("No need to produce, quitting!")
    case productionChan <- 1:
    }
}

这个想法是单个消费者goroutine迭代所需大小的有效负载切片,在切片填满后,循环终止并关闭退出通道。所有生成器都在select语句中被阻止,该语句是关于是否从退出通道发送或接收。当戒烟通道关闭时,每个使用该退出通道启动的生产者将立即退出。

如果您的生产者数量较少,每个生成多个值,那么这个习惯用法也应该很容易修改。

答案 2 :(得分:0)

您可以使用select尝试发送到缓冲频道:如果选择发送且频道缓冲区已满,您将会看到default案例:

//this goroutine sends to the channel until it can't
func f(c chan int, wg *sync.WaitGroup) {

    for i := 0; ; i++ {
        select {
        case c <- i: //sent successfully
            continue
        default:
            fmt.Println("Can't send, aborting!")
            wg.Done()
            return
        }
    }
}

func main() {

    c := make(chan int, 100)
    wg := sync.WaitGroup{}
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go f(c, &wg)
    }

    wg.Wait()
    fmt.Println("Done!")

}

这种方法的缺点是,如果消费者在你完成生产之前开始消费,你将进入无限循环。

您还可以关闭来自消费者方的频道,导致制作人恐慌,并抓住这种恐慌:

func f(c chan int) {

    defer func() {
        _ = recover()
        fmt.Println("Done!")
    }()

    for i := 0; ; i++ {
        c <- i
    }
}

func main() {

    c := make(chan int)

    for i := 0; i < 10; i++ {

        go f(c)
    }

    n := 0
    for {
        <-c
        n++
        if n > 100 {
            close(c)
            break
        }
    }

但是如果你只是想生产不超过给定数量的物品,为什么不生产N个goroutines,每个产生1个项目?或产生K goroutines每个产生N / K项目?最好事先了解这一点。

答案 3 :(得分:-1)

免责声明:这不是惯用的,但我并不建议在实践中使用此代码。那说......

最近介绍了reflect.Value.TryRecvreflect.Value.TrySend。他们完全按照你的意愿行事。

products := make(chan int, 100)

for i := 0; i < 10; i++ {
    go func() {
        for {
            if !reflect.ValueOf(products).TrySend(1) {
                return
            }
        }
    }()
}

查看go playground上运行的代码。