英文:
How to write a better two channel select
问题
在以下代码中,有两个通道A和B,它们包含工作任务。在实际代码中,它们是不同的结构体。工作者需要在退出之前排空这两个通道。工作者需要处理来自这两个通道的信息。两个select语句可以工作,但是它们非常笨拙。如果我添加default:
使它们非阻塞,那么代码将无法排空通道。有没有更好的编写select语句的方法?
现在,如果通道A没有工作任务,那么通道B也不会被处理。这是另一个要解决的问题,但不是我主要关注的问题。
以下是用于测试以下代码的playground:
package main
import (
"fmt"
"time"
)
const (
fillCount = 10 // 每个输入通道中的元素数量
numWorkers = 3 // 消费者数量
)
func Wait() {
time.Sleep(2000 * time.Millisecond)
}
func fillChannel(work chan string, name string) {
for i := 0; i < fillCount; i++ {
work <- fmt.Sprintf("%s%d", name, i)
}
close(work) // 我们完成了
}
func doWork(id int, ch1 chan string, ch2 chan string, done chan bool) {
fmt.Println("Running worker", id)
defer fmt.Println("Ending worker", id)
for ch1Open, ch2Open := true, true; ch1Open && ch2Open; {
cnt1 := len(ch1)
cnt2 := len(ch2)
if ch1Open {
select {
case str, more := <-ch1:
if more {
fmt.Printf("%d: ch1(%d) %s\n", id, cnt1, str)
} else {
fmt.Printf("%d: ch1 closed\n", id)
ch1Open = false
}
}
}
if ch2Open {
select {
case str, more := <-ch2:
if more {
fmt.Printf("%d: ch2(%d) %s\n", id, cnt2, str)
} else {
fmt.Printf("%d: ch2 closed\n", id)
ch2Open = false
}
}
}
}
done <- true
}
func main() {
a := make(chan string, 2) // 一个小的通道
b := make(chan string, 5) // 一个较大的通道
// 生成工作任务
go fillChannel(a, "A")
go fillChannel(b, "B")
// 启动消费者
done := make(chan bool)
for i := 0; i < numWorkers; i++ {
go doWork(i, a, b, done)
}
// 等待goroutine完成
for i := 0; i < numWorkers; i++ {
<-done
}
fmt.Println("All workers done.")
Wait() // 如果没有这个,工作者的延迟打印语句将不会刷新
}
英文:
In the following code there are two channels A & B that contain work, in the real code they are different structures, the workers need to drain both channels before quitting. The workers need the information coming in from both channels. The two select statements work but it's very clumsy. If I add default:
to make them non-blocking then the code fails to drain the channels. Is there a better way of writing the selects?
Right now if channel A has no work then channel B does not get serviced either. Another problem to solve, but not my main concern.
playground for testing following code:
package main
import (
"fmt"
"time"
)
const (
fillCount = 10 // number of elements in each input channel
numWorkers = 3 // number of consumers.
)
func Wait() {
time.Sleep(2000 * time.Millisecond)
}
func fillChannel(work chan string, name string) {
for i := 0; i < fillCount; i++ {
work <- fmt.Sprintf("%s%d", name, i)
}
close(work) // we're finished
}
func doWork(id int, ch1 chan string, ch2 chan string, done chan bool) {
fmt.Println("Running worker", id)
defer fmt.Println("Ending worker", id)
for ch1Open, ch2Open := true, true; ch1Open && ch2Open; {
cnt1 := len(ch1)
cnt2 := len(ch2)
if ch1Open {
select {
case str, more := <-ch1:
if more {
fmt.Printf("%d: ch1(%d) %s\n", id, cnt1, str)
} else {
fmt.Printf("%d: ch1 closed\n", id)
ch1Open = false
}
}
}
if ch2Open {
select {
case str, more := <-ch2:
if more {
fmt.Printf("%d: ch2(%d) %s\n", id, cnt2, str)
} else {
fmt.Printf("%d: ch2 closed\n", id)
ch2Open = false
}
}
}
}
done <- true
}
func main() {
a := make(chan string, 2) // a small channel
b := make(chan string, 5) // a bigger channel
// generate work
go fillChannel(a, "A")
go fillChannel(b, "B")
// launch the consumers
done := make(chan bool)
for i := 0; i < numWorkers; i++ {
go doWork(i, a, b, done)
}
// wait for the goroutines to finish.
for i := 0; i < numWorkers; i++ {
<-done
}
fmt.Println("All workers done.")
Wait() // without this the defered prints from the workers doesn't flush
}
答案1
得分: 8
在循环中选择两个通道。当一个通道关闭时,将通道变量设置为nil,使得该通道上的接收操作不可用。当两个通道都为nil时,退出循环。
package main
import (
"fmt"
"time"
)
const (
fillCount = 10 // 每个输入通道中的元素数量
numWorkers = 3 // 消费者数量
)
func fillChannel(work chan string, name string) {
for i := 0; i < fillCount; i++ {
work <- fmt.Sprintf("%s%d", name, i)
}
close(work) // 我们完成了
}
func doWork(id int, ch1 chan string, ch2 chan string, done chan bool) {
fmt.Println("Running worker", id)
for ch1 != nil || ch2 != nil {
select {
case str, ok := <-ch1:
if ok {
fmt.Printf("%d: ch1(%d) %s\n", id, len(ch1), str)
} else {
ch1 = nil
fmt.Printf("%d: ch1 closed\n", id)
}
case str, ok := <-ch2:
if ok {
fmt.Printf("%d: ch2(%d) %s\n", id, len(ch2), str)
} else {
ch2 = nil
fmt.Printf("%d: ch2 closed\n", id)
}
}
}
fmt.Println("Ending worker", id)
done <- true
}
func main() {
a := make(chan string, 2) // 一个小的通道
b := make(chan string, 5) // 一个较大的通道
// 生成任务
go fillChannel(a, "A")
go fillChannel(b, "B")
// 启动消费者
done := make(chan bool)
for i := 0; i < numWorkers; i++ {
go doWork(i, a, b, done)
}
// 等待goroutine完成
for i := 0; i < numWorkers; i++ {
<-done
}
fmt.Println("所有工作已完成。")
}
英文:
Select on both channels in a loop. When a channel is closed, set the channel variable to nil to make receive on that channel not ready. Break out of the loop when both channels are nil.
http://play.golang.org/p/9gRY1yKqJ9
package main
import (
"fmt"
"time"
)
const (
fillCount = 10 // number of elements in each input channel
numWorkers = 3 // number of consumers.
)
func fillChannel(work chan string, name string) {
for i := 0; i < fillCount; i++ {
work <- fmt.Sprintf("%s%d", name, i)
}
close(work) // we're finished
}
func doWork(id int, ch1 chan string, ch2 chan string, done chan bool) {
fmt.Println("Running worker", id)
for ch1 != nil || ch2 != nil {
select {
case str, ok := <-ch1:
if ok {
fmt.Printf("%d: ch1(%d) %s\n", id, len(ch1), str)
} else {
ch1 = nil
fmt.Printf("%d: ch1 closed\n", id)
}
case str, ok := <-ch2:
if ok {
fmt.Printf("%d: ch2(%d) %s\n", id, len(ch2), str)
} else {
ch2 = nil
fmt.Printf("%d: ch2 closed\n", id)
}
}
}
fmt.Println("Ending worker", id)
done <- true
}
func main() {
a := make(chan string, 2) // a small channel
b := make(chan string, 5) // a bigger channel
// generate work
go fillChannel(a, "A")
go fillChannel(b, "B")
// launch the consumers
done := make(chan bool)
for i := 0; i < numWorkers; i++ {
go doWork(i, a, b, done)
}
// wait for the goroutines to finish.
for i := 0; i < numWorkers; i++ {
<-done
}
fmt.Println("All workers done.")
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论