Concurrency 围棋中生产者/消费者最整洁的习惯用法是什么?
我想做的是有一套制作人goroutines(其中一些可能完成,也可能没有完成)和一套消费者例程。问题在于括号中的警告——我们不知道返回答案的总数 所以我想做的是:Concurrency 围棋中生产者/消费者最整洁的习惯用法是什么?,concurrency,go,Concurrency,Go,我想做的是有一套制作人goroutines(其中一些可能完成,也可能没有完成)和一套消费者例程。问题在于括号中的警告——我们不知道返回答案的总数 所以我想做的是: package main import ( "fmt" "math/rand" ) func producer(c chan int) { // May or may not produce. success := rand.Float32() > 0.5 if success { c <-
package main
import (
"fmt"
"math/rand"
)
func producer(c chan int) {
// May or may not produce.
success := rand.Float32() > 0.5
if success {
c <- rand.Int()
}
}
func main() {
c := make(chan int, 10)
for i := 0; i < 10; i++ {
go producer(c, signal)
}
// If we include a close, then that's WRONG. Chan will be closed
// but a producer will try to write to it. Runtime error.
close(c)
// If we don't close, then that's WRONG. All goroutines will
// deadlock, since the range keyword will look for a close.
for num := range c {
fmt.Printf("Producer produced: %d\n", num)
}
fmt.Println("All done.")
}
只有制片人应该关闭频道。您可以通过调用consumer来实现您的目标,一旦您的生产者启动,consumer就会在结果通道上迭代(
range
)。在主线程中,您等待(请参见sync.WaitGroup
),直到您的消费者/生产者完成他们的工作。制作人完成后,您将关闭结果频道,这将迫使您的消费者退出(range
将在频道关闭且没有剩余缓冲项时退出)
示例代码:
package main
import (
"log"
"sync"
"time"
"math/rand"
"runtime"
)
func consumer() {
defer consumer_wg.Done()
for item := range resultingChannel {
log.Println("Consumed:", item)
}
}
func producer() {
defer producer_wg.Done()
success := rand.Float32() > 0.5
if success {
resultingChannel <- rand.Int()
}
}
var resultingChannel = make(chan int)
var producer_wg sync.WaitGroup
var consumer_wg sync.WaitGroup
func main() {
rand.Seed(time.Now().Unix())
for c := 0; c < runtime.NumCPU(); c++ {
producer_wg.Add(1)
go producer()
}
for c := 0; c < runtime.NumCPU(); c++ {
consumer_wg.Add(1)
go consumer()
}
producer_wg.Wait()
close(resultingChannel)
consumer_wg.Wait()
}
主程序包
进口(
“日志”
“同步”
“时间”
“数学/兰德”
“运行时”
)
func消费者(){
延迟消费者工作组完成()
对于项:=范围结果通道{
log.Println(“已消耗:”,项)
}
}
func生产者(){
延迟生产商工作组完成()
成功:=rand.Float32()>0.5
如果成功{
结果通道解决这些问题的方法总是很多。下面是一个使用简单同步通道的解决方案,这些通道在Go中是基本的。没有缓冲通道,没有关闭通道,没有等待组
它离你的“满嘴的”解决方案并不远,而且——很抱歉让你失望——也没那么小。它确实把消费者放在了自己的goroutine中,这样消费者就可以在生产者生产数字时消费数字。它还区分了生产“尝试”可以以成功或失败告终。如果生产失败,则会立即进行尝试。如果成功,则在消耗该号码之前不会进行尝试
package main
import (
"fmt"
"math/rand"
)
func producer(c chan int, fail chan bool) {
if success := rand.Float32() > 0.5; success {
c <- rand.Int()
} else {
fail <- true
}
}
func consumer(c chan int, success chan bool) {
for {
num := <-c
fmt.Printf("Producer produced: %d\n", num)
success <- true
}
}
func main() {
const nTries = 10
c := make(chan int)
done := make(chan bool)
for i := 0; i < nTries; i++ {
go producer(c, done)
}
go consumer(c, done)
for i := 0; i < nTries; i++ {
<-done
}
fmt.Println("All done.")
}
主程序包
进口(
“fmt”
“数学/兰德”
)
func制作人(c chan int,fail chan bool){
如果成功:=rand.Float32()>0.5;成功{
c我之所以添加这一点,是因为现有的答案没有明确说明几件事。首先,codewalk示例中的range循环只是一个无限事件循环,需要不断地重新检查和更新相同的url列表
接下来,一个通道本身已经是Go中惯用的消费者-生产者队列。支持该通道的异步缓冲区的大小决定了生产者在获得背压之前可以产生多少。在下面设置N=0,以查看锁步生产者-消费者,而不让任何人跑在前面或后面。实际上,N=10将让生产者p阻塞前最多生产10种产品
最后,有一些很好的习惯用法,用于在Go中编写通信顺序处理程序(例如,为您启动Go例程的函数,以及使用for/select模式通信和接受控制命令)。我认为WaitGroups很笨拙,希望看到习惯用法的示例
package main
import (
"fmt"
"time"
)
type control int
const (
sleep control = iota
die // receiver will close the control chan in response to die, to ack.
)
func (cmd control) String() string {
switch cmd {
case sleep: return "sleep"
case die: return "die"
}
return fmt.Sprintf("%d",cmd)
}
func ProduceTo(writechan chan<- int, ctrl chan control, done chan bool) {
var product int
go func() {
for {
select {
case writechan <- product:
fmt.Printf("Producer produced %v\n", product)
product++
case cmd:= <- ctrl:
fmt.Printf("Producer got control cmd: %v\n", cmd)
switch cmd {
case sleep:
fmt.Printf("Producer sleeping 2 sec.\n")
time.Sleep(2000 * time.Millisecond)
case die:
fmt.Printf("Producer dies.\n")
close(done)
return
}
}
}
}()
}
func ConsumeFrom(readchan <-chan int, ctrl chan control, done chan bool) {
go func() {
var product int
for {
select {
case product = <-readchan:
fmt.Printf("Consumer consumed %v\n", product)
case cmd:= <- ctrl:
fmt.Printf("Consumer got control cmd: %v\n", cmd)
switch cmd {
case sleep:
fmt.Printf("Consumer sleeping 2 sec.\n")
time.Sleep(2000 * time.Millisecond)
case die:
fmt.Printf("Consumer dies.\n")
close(done)
return
}
}
}
}()
}
func main() {
N := 10
q := make(chan int, N)
prodCtrl := make(chan control)
consCtrl := make(chan control)
prodDone := make(chan bool)
consDone := make(chan bool)
ProduceTo(q, prodCtrl, prodDone)
ConsumeFrom(q, consCtrl, consDone)
// wait for a moment, to let them produce and consume
timer := time.NewTimer(10 * time.Millisecond)
<-timer.C
// tell producer to pause
fmt.Printf("telling producer to pause\n")
prodCtrl <- sleep
// wait for a second
timer = time.NewTimer(1 * time.Second)
<-timer.C
// tell consumer to pause
fmt.Printf("telling consumer to pause\n")
consCtrl <- sleep
// tell them both to finish
prodCtrl <- die
consCtrl <- die
// wait for that to actually happen
<-prodDone
<-consDone
}
主程序包
进口(
“fmt”
“时间”
)
类型控制int
常数(
睡眠控制=物联网
die//接收器将关闭控制通道,以响应die,确认。
)
func(cmd控件)String()字符串{
开关指令{
案例睡眠:返回“睡眠”
案例模具:返回“模具”
}
返回fmt.Sprintf(“%d”,cmd)
}
func ProduceTo(writechan chan如果使用带fanIn函数的生成器模式,则可以使用简单的无缓冲通道,而不使用等待组
在generator模式中,每个生产者返回一个通道并负责关闭它。fanIn函数然后在这些通道上迭代,并将返回的值转发到它返回的单个通道上
当然,问题是当每个通道都关闭时,fanIn函数会转发通道类型(int)的零值
您可以通过使用通道类型的零值作为sentinel值并仅使用来自fanIn通道的结果(如果它们不是零值)来解决此问题
下面是一个例子:
package main
import (
"fmt"
"math/rand"
)
const offset = 1
func producer() chan int {
cout := make(chan int)
go func() {
defer close(cout)
// May or may not produce.
success := rand.Float32() > 0.5
if success {
cout <- rand.Int() + offset
}
}()
return cout
}
func fanIn(cin []chan int) chan int {
cout := make(chan int)
go func() {
defer close(cout)
for _, c := range cin {
cout <- <-c
}
}()
return cout
}
func main() {
chans := make([]chan int, 0)
for i := 0; i < 10; i++ {
chans = append(chans, producer())
}
for num := range fanIn(chans) {
if num > offset {
fmt.Printf("Producer produced: %d\n", num)
}
}
fmt.Println("All done.")
}
主程序包
进口(
“fmt”
“数学/兰德”
)
常数偏移=1
func producer()chan int{
cout:=make(chan int)
go func(){
延迟关闭(cout)
//可能产生也可能不产生。
成功:=rand.Float32()>0.5
如果成功{
coutproducer-consumer是一种非常常见的模式,我编写了一个库,以便仔细处理chan通信。例如:
func main(){
maxLoop:=10
var wg sync.WaitGroup
wg.Add(maxLoop)
延迟工作组等待()
使用者:=func(ls[]接口{})错误{
fmt.Printf(“获取%+v\n”,ls)
工作组添加(-len(ls))
归零
}
conf:=prosumer.DefaultConfig(prosumer.Consumer(Consumer))
c:=prosumer.NewCoordinator(conf)
c、 开始()
延迟c.Close(真)
对于i:=0;i
close有一个名为“优雅”的参数,这意味着是否会耗尽潜在的chan。您好,谢谢您的回答-这确实是我想要的。您能否进一步说明您的建议“只有制片人才应该关闭频道”-这听起来像是一个常识/代码有意义的规则,但我想知道是否还有一个技术原因儿子(因为您列出的代码示例具有关闭频道的主要功能)。再次感谢!啊,对,这是有意义的。我想这可能是一条很难的规则——每个制作人都必须检查是否允许关闭频道(因此最后一个完成的制作人会关闭频道)。这显然要混乱得多(进行了更多不必要的检查)而不仅仅是在我们的示例中的main()中关闭它,但我担心这是一种做事的方式(出于某些我不知道的原因)。我仍在尝试了解样式最佳实践。添加了更多信息
package main
import (
"fmt"
"time"
)
type control int
const (
sleep control = iota
die // receiver will close the control chan in response to die, to ack.
)
func (cmd control) String() string {
switch cmd {
case sleep: return "sleep"
case die: return "die"
}
return fmt.Sprintf("%d",cmd)
}
func ProduceTo(writechan chan<- int, ctrl chan control, done chan bool) {
var product int
go func() {
for {
select {
case writechan <- product:
fmt.Printf("Producer produced %v\n", product)
product++
case cmd:= <- ctrl:
fmt.Printf("Producer got control cmd: %v\n", cmd)
switch cmd {
case sleep:
fmt.Printf("Producer sleeping 2 sec.\n")
time.Sleep(2000 * time.Millisecond)
case die:
fmt.Printf("Producer dies.\n")
close(done)
return
}
}
}
}()
}
func ConsumeFrom(readchan <-chan int, ctrl chan control, done chan bool) {
go func() {
var product int
for {
select {
case product = <-readchan:
fmt.Printf("Consumer consumed %v\n", product)
case cmd:= <- ctrl:
fmt.Printf("Consumer got control cmd: %v\n", cmd)
switch cmd {
case sleep:
fmt.Printf("Consumer sleeping 2 sec.\n")
time.Sleep(2000 * time.Millisecond)
case die:
fmt.Printf("Consumer dies.\n")
close(done)
return
}
}
}
}()
}
func main() {
N := 10
q := make(chan int, N)
prodCtrl := make(chan control)
consCtrl := make(chan control)
prodDone := make(chan bool)
consDone := make(chan bool)
ProduceTo(q, prodCtrl, prodDone)
ConsumeFrom(q, consCtrl, consDone)
// wait for a moment, to let them produce and consume
timer := time.NewTimer(10 * time.Millisecond)
<-timer.C
// tell producer to pause
fmt.Printf("telling producer to pause\n")
prodCtrl <- sleep
// wait for a second
timer = time.NewTimer(1 * time.Second)
<-timer.C
// tell consumer to pause
fmt.Printf("telling consumer to pause\n")
consCtrl <- sleep
// tell them both to finish
prodCtrl <- die
consCtrl <- die
// wait for that to actually happen
<-prodDone
<-consDone
}
package main
import (
"fmt"
"math/rand"
)
const offset = 1
func producer() chan int {
cout := make(chan int)
go func() {
defer close(cout)
// May or may not produce.
success := rand.Float32() > 0.5
if success {
cout <- rand.Int() + offset
}
}()
return cout
}
func fanIn(cin []chan int) chan int {
cout := make(chan int)
go func() {
defer close(cout)
for _, c := range cin {
cout <- <-c
}
}()
return cout
}
func main() {
chans := make([]chan int, 0)
for i := 0; i < 10; i++ {
chans = append(chans, producer())
}
for num := range fanIn(chans) {
if num > offset {
fmt.Printf("Producer produced: %d\n", num)
}
}
fmt.Println("All done.")
}