问题
I wish to have a go routine listening on two channels, blocked when both channels are drained. However, if both channels contains data, I want one to be drained before the other is handled.
In the working example below I wish all out
to be drained before exit
is handled. I use a select
-statement which doesn't have any priority order. How might I get around the problem, making all 10 out-values be handled before the exit?
package main
import "fmt"
func sender(out chan int, exit chan bool){
for i := 1; i <= 10; i++ {
out <- i
}
exit <- true
}
func main(){
out := make(chan int, 10)
exit := make(chan bool)
go sender(out, exit)
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
fmt.Println("Exiting")
break L
}
}
fmt.Println("Did we get all 10? Most likely not")
}
回答1:
package main
import "fmt"
func sender(out chan int, exit chan bool) {
for i := 1; i <= 10; i++ {
out <- i
}
exit <- true
}
func main() {
out := make(chan int, 10)
exit := make(chan bool)
go sender(out, exit)
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
continue
default:
}
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
continue
case <-exit:
fmt.Println("Exiting")
}
break
}
fmt.Println("Did we get all 10? I think so!")
}
The default case of the first select makes it non-blocking. The select will drain the out channel without looking at the exit channel, but otherwise will not wait. If the out channel is empty, it immediately drops to the second select. The second select is blocking. It will wait for data on either channel. If an exit comes, it handles it and allows the loop to exit. If data comes, it goes back up the top of the loop and back into drain mode.
回答2:
the language supports this natively and no workaround is required. It's very simple: the quit channel should only be visible to the producer. On quit, the producer closes the channel. Only when the channel is empty and closed does the consumer quit. This is made possible by reading from the channel as follows:
v, ok := <-c
this will set ok
to a boolean indicating whether or not the value v
was actually read off the channel (ok == true
), or if v
was set to the zero-value of the type handled by the channel c
because c
is closed and empty (ok == false
). When the channel is closed and non-empty, v
will be a valid value and ok
will be true
. When the channel is closed and empty, v
will be the zero-value of the type handled by the channel c
, and ok
will be false
, indicating that v
is useless.
Here is an example to illustrate:
package main
import (
"fmt"
"math/rand"
"time"
)
var (
produced = 0
processed = 0
)
func produceEndlessly(out chan int, quit chan bool) {
defer close(out)
for {
select {
case <-quit:
fmt.Println("RECV QUIT")
return
default:
out <- rand.Int()
time.Sleep(time.Duration(rand.Int63n(5e6)))
produced++
}
}
}
func quitRandomly(quit chan bool) {
d := time.Duration(rand.Int63n(5e9))
fmt.Println("SLEEP", d)
time.Sleep(d)
fmt.Println("SEND QUIT")
quit <- true
}
func main() {
vals, quit := make(chan int, 10), make(chan bool)
go produceEndlessly(vals, quit)
go quitRandomly(quit)
for {
x, ok := <-vals
if !ok {
break
}
fmt.Println(x)
processed++
time.Sleep(time.Duration(rand.Int63n(5e8)))
}
fmt.Println("Produced:", produced)
fmt.Println("Processed:", processed)
}
this is documented in the "Receive Operator" section of the go spec: http://golang.org/ref/spec#Receive_operator
回答3:
Another approach:
package main
import "fmt"
func sender(c chan int) chan int {
go func() {
for i := 1; i <= 15; i++ {
c <- i
}
close(c)
}()
return c
}
func main() {
for i := range sender(make(chan int, 10)) {
fmt.Printf("Value: %d\n", i)
}
fmt.Println("Did we get all 15? Surely yes")
}
$ go run main.go
Value: 1
Value: 2
Value: 3
Value: 4
Value: 5
Value: 6
Value: 7
Value: 8
Value: 9
Value: 10
Value: 11
Value: 12
Value: 13
Value: 14
Value: 15
Did we get all 15? Surely yes
$
回答4:
I have created one rather simple workaround. It does what I want, but if anyone else has a better solution, please let me know:
exiting := false
for !exiting || len(out)>0 {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
exiting = true
fmt.Println("Exiting")
}
}
Instead of exiting on receiving, I flag an exit, exiting once I've made sure nothing is left in chan out
.
回答5:
Here's another option.
Consumer Code:
go func() {
stop := false
for {
select {
case item, _ := <-r.queue:
doWork(item)
case <-r.stopping:
stop = true
}
if stop && len(r.queue) == 0 {
break
}
}
}()
回答6:
In my case, I really wanted to prioritise data from one channel over another, and not just have an out-of-band exit signal. For the benefit of anyone else with the same issue I think this approach works without the potential race condition:
OUTER:
for channelA != nil || channelB != nil {
select {
case typeA, ok := <-channelA:
if !ok {
channelA = nil
continue OUTER
}
doSomething(typeA)
case nodeIn, ok := <-channelB:
if !ok {
channelB = nil
continue OUTER
}
// Looped non-blocking nested select here checks that channelA
// really is drained before we deal with the data from channelB
NESTED:
for {
select {
case typeA, ok := <-channelA:
if !ok {
channelA = nil
continue NESTED
}
doSomething(typeA)
default:
// We are free to process the typeB data now
doSomethingElse(typeB)
break NESTED
}
}
}
}
回答7:
I think Sonia's answer is incorrect.This is my solution,a little bit complicate.
package main
import "fmt"
func sender(out chan int, exit chan bool){
for i := 1; i <= 10; i++ {
out <- i
}
exit <- true
}
func main(){
out := make(chan int, 10)
exit := make(chan bool)
go sender(out, exit)
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
for{
select{
case i:=<-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
fmt.Println("Exiting")
break L
}
}
fmt.Println("Did we get all 10? Yes!")
}
回答8:
Is there any specific reason for using a buffered channel make(chan int, 10)
?
You need to use an unbuffered channel vs buffered, which you are using.
Just remove 10
, it should be just make(chan int)
.
This way execution in the sender
function can only proceed to the exit <- true
statement after the last message from the out
channel is dequeued by the i := <-out
statement. If that statement has not been executed, there is no way the exit <- true
could be reached in the goroutine.
来源:https://stackoverflow.com/questions/11117382/priority-in-go-select-statement-workaround