我有一个经典的“生产者 - 消费者”问题的变体。在我的计划中,有10个生产者并行工作,他们的目标是总共生产N个产品。
我考虑过使用缓冲通道:
products := make([]int, 100) // In total, produce 100 products
// The producers
for i := 0; i < 10; i++ {
go func() {
products <- 1 // !!
}()
}
然而,它不起作用:
if len(products) < 100 { products <- 1 }
不是原子操作,因此无效。还有其他方法吗?
答案 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.TryRecv和reflect.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上运行的代码。