我希望在两个通道上进行常规监听,当两个通道都耗尽时阻塞。但是,如果两个通道都包含数据,我希望在处理另一个通道之前将其耗尽。
在下面的工作示例中,我希望在处理out
之前排空所有exit
。我使用select
- 没有任何优先顺序的语句。我如何解决问题,在退出之前处理所有10个输出值?
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")
}
答案 0 :(得分:32)
该语言本身支持此功能,无需解决方法。这很简单:退出通道应该只对生产者可见。退出时,制作人关闭频道。只有当渠道为空并关闭时,消费者才会退出。这可以通过如下通道读取来实现:
v, ok := <-c
这会将ok
设置为一个布尔值,指示值v
是否实际从频道(ok == true
)读取,或者v
是否设置为频道c
处理的类型的零值,因为c
已关闭且为空(ok == false
)。当频道关闭且非空时,v
将是有效值,ok
将为true
。当频道关闭且为空时,v
将是频道c
处理的类型的零值,而ok
将是false
,表示v
1}}没用。
这是一个例子来说明:
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)
}
这是在go规范的“接收运算符”部分中记录的:http://golang.org/ref/spec#Receive_operator
答案 1 :(得分:22)
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!")
}
第一个选择的默认情况使其无阻塞。选择将在不看出口通道的情况下排出出口通道,否则将不会等待。如果out通道为空,它会立即下降到第二个选择。第二个选择是阻止。它将等待任一通道上的数据。如果出口,它会处理它并允许循环退出。如果数据到来,它会回到循环的顶部并返回到排水模式。
答案 2 :(得分:5)
另一种方法:
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
$
答案 3 :(得分:1)
我创建了一个相当简单的解决方法。它做我想要的,但如果其他人有更好的解决方案,请告诉我:
exiting := false
for !exiting || len(out)>0 {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
exiting = true
fmt.Println("Exiting")
}
}
我没有退出接收,而是标记退出,一旦我确定chan out
中没有留下任何内容就退出。
答案 4 :(得分:1)
是的,至少可以说是不好听,但是可以做到100%,没有陷阱,没有隐藏的限制。
这是一个简短的代码示例,说明如下。
package main
import(
"fmt"
"time"
)
func sender(out chan int, exit chan bool) {
for i := 1; i <= 10; i++ {
out <- i
}
time.Sleep(2000 * time.Millisecond)
out <- 11
exit <- true
}
func main(){
out := make(chan int, 20)
exit := make(chan bool)
go sender(out, exit)
time.Sleep(500 * time.Millisecond)
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
}
fmt.Println("Did we get all 10? Yes.")
fmt.Println("Did we get 11? DEFINITELY YES")
}
main()
带有注释:func main(){
out := make(chan int, 20)
exit := make(chan bool)
go sender(out, exit)
time.Sleep(500 * time.Millisecond)
L:
for {
select {
// here we go when entering next loop iteration
// and check if the out has something to be read from
// this select is used to handle buffered data in a loop
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
// else we fallback in here
select {
// this select is used to block when there's no data in either chan
case i := <-out:
// if out has something to read, we unblock, and then go the loop round again
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
// this select is used to explicitly propritize one chan over the another,
// in case we woke up (unblocked up) on the low-priority case
// NOTE:
// this will prioritize high-pri one even if it came _second_, in quick
// succession to the first one
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
}
fmt.Println("Did we get all 10? Yes.")
fmt.Println("Did we get 11? DEFINITELY YES")
}
注意::在使用优先级玩弄技巧之前,请确保您正在解决正确的问题。
机会是,可以用不同的方式解决。
仍然,在Go中优先选择select是一件很棒的事情。只是一个梦..
注意::此线程上的答案https://stackoverflow.com/a/45854345/11729048非常相似,但只有两个 select
-嵌套,而不嵌套和我一样三个。有什么不同?我的方法效率更高,而且我们明确希望在每次循环迭代时都能处理随机选择。
但是,如果未缓冲高优先级通道,和/或您不希望在其上存储大量数据,则只有零星的单个事件, 那么更简单的两阶段习语(如该答案)就足够了:
L:
for {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
default:
fmt.Println("Exiting")
break L
}
}
}
基本上是2个和3个阶段,其中1个已被删除。
再说一次:在90%的情况下,您认为确实需要对chan switch情况进行优先排序,而实际上并不需要。
这是一个单行代码,可以包装在宏中:
for {
select { case a1 := <-ch_p1: p1_action(a1); default: select { case a1 := <-ch_p1: p1_action(a1); case a2 := <-ch_p2: select { case a1 := <-ch_p1: p1_action(a1); default: p2_action(a2); }}}
}
然后您有两个选择。第一个-使用中间goroutines构建一棵树,以使每个fork都是二进制的(上述惯用法)。
第二个选项是使优先级叉多然后加倍。
这是三个优先事项的示例:
for {
select {
case a1 := <-ch_p1:
p1_action(a1)
default:
select {
case a2 := <-ch_p2:
p2_action(a2)
default:
select { // block here, on this select
case a1 := <-ch_p1:
p1_action(a1)
case a2 := <-ch_p2:
select {
case a1 := <-ch_p1:
p1_action(a1)
default:
p2_action(a2)
}
case a3 := <-ch_p3:
select {
case a1 := <-ch_p1:
p1_action(a1)
case a2 := <-ch_p2:
p1_action(a2)
default:
p2_action(a3)
}
}
}
}
}
也就是说,整个结构在概念上分为三个部分,即原始(二进制)部分。
P.S。的修辞性问题:为什么Golang没有将其内置到语言中???这个问题很夸张。
答案 5 :(得分:0)
在我的情况下,我真的想要优先考虑来自一个频道的数据而不是另一个频道的数据,而不仅仅是有一个带外退出信号。为了其他任何有相同问题的人的利益,我认为这种方法在没有潜在竞争条件的情况下有效:
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
}
}
}
}
答案 6 :(得分:0)
我认为索尼娅的回答是不正确的。这是我的解决方案,有点复杂。
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!")
}
答案 7 :(得分:0)
使用缓冲频道make(chan int, 10)
是否有任何特定原因?
您需要使用正在使用的无缓冲通道vs buffered。
只需删除10
,它应该只是make(chan int)
。
这样sender
函数中的执行只能在 exit <- true
通道中的最后一条消息被{{出列'之后继续执行out
语句 1}}陈述。如果该语句尚未执行,则无法在goroutine中访问i := <-out
。
答案 8 :(得分:0)
这是另一种选择。
消费者代码:
go func() {
stop := false
for {
select {
case item, _ := <-r.queue:
doWork(item)
case <-r.stopping:
stop = true
}
if stop && len(r.queue) == 0 {
break
}
}
}()