Priorytet w obejściu instrukcji Go select
Chciałbym mieć rutynowe słuchanie na dwóch kanałach, zablokowane, gdy oba kanały są opróżnione. Jeśli jednak oba kanały zawierają dane, chcę, aby jeden z nich został opróżniony przed drugim.
W przykładzie poniżej chciałbym, aby wszystkie out
zostały osuszone przed exit
jest obsługiwane. Używam select
-instrukcji, która nie ma żadnego priorytetu. Jak mogę obejść problem, sprawiając, że wszystkie 10 out-wartości będą obsługiwane przed zakończeniem?
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")
}
8 answers
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!")
}
Domyślna wielkość liter pierwszego wyboru sprawia, że nie blokuje się. Select opróżni kanał wyjściowy bez patrzenia na kanał wyjściowy, ale w przeciwnym razie nie będzie czekał. Jeśli kanał wyjściowy jest pusty, natychmiast spada do drugiego wyboru. Drugi wybór to blokowanie. Będzie czekać na dane na każdym kanale. Jeśli pojawi się wyjście, obsługuje je i pozwala pętli wyjść. Jeśli pojawią się dane, powróci do górnej części pętli i z powrotem do trybu drenażu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-20 14:41:16
Język Obsługuje to natywnie i nie jest wymagane obejście. To bardzo proste: kanał quit powinien być widoczny tylko dla producenta. Na koniec producent zamyka kanał. Tylko wtedy, gdy kanał jest pusty i zamknięty, konsument kończy pracę. Jest to możliwe dzięki odczytaniu z kanału w następujący sposób:
v, ok := <-c
Ustawia ok
na wartość logiczną wskazującą, czy wartość v
została rzeczywiście odczytana z kanału (ok == true
), Czy v
została ustawiona na wartość zero-wartość typu obsługiwanego przez kanał c
ponieważ c
jest zamknięty i pusty (ok == false
). Gdy kanał jest zamknięty i niepusty, v
będzie poprawną wartością, a ok
będzie true
. Gdy kanał jest zamknięty i pusty, v
będzie wartością zerową typu obsługiwanego przez kanał c
, A ok
będzie wartością false
, co oznacza, że v
jest bezużyteczny.
Oto przykład do zilustrowania:
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)
}
Jest to udokumentowane w sekcji "Operator odbioru" specyfikacji go: http://golang.org/ref/spec#Receive_operator
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-21 23:13:52
Inne podejście:
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
$
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-20 10:45:57
Stworzyłem jedno dość proste obejście. Robi to, co chcę, ale jeśli ktoś ma lepsze rozwiązanie, proszę dać mi znać: {]}
exiting := false
for !exiting || len(out)>0 {
select {
case i := <-out:
fmt.Printf("Value: %d\n", i)
case <-exit:
exiting = true
fmt.Println("Exiting")
}
}
Zamiast wyjść przy odbiorze, zaznaczam wyjście, wychodząc, gdy upewnię się, że nic nie zostało w chan out
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-20 12:30:47
W moim przypadku, naprawdę chciałem nadać priorytet danych z jednego kanału nad drugim, a nie tylko mieć sygnał wyjściowy poza pasmem. Dla dobra innych z tym samym problemem myślę, że to podejście działa bez potencjalnego warunku rasy:
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
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 10:29:15
Myślę, że odpowiedź Sonii jest błędna.To jest moje rozwiązanie, trochę skomplikowane.
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!")
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-24 06:23:18
Czy jest jakiś konkretny powód używania buforowanego kanału make(chan int, 10)
?
Musisz użyć kanału niebuforowanego vs buforowanego, którego używasz.
Po prostu usuń 10
, powinno być po prostu make(chan int)
.
W ten sposób wykonanie funkcji sender
może przebiegać tylko do instrukcji exit <- true
po Ostatnia wiadomość z kanału out
zostanie odebrana przez instrukcję i := <-out
. Jeśli to stwierdzenie nie zostało wykonane, nie ma możliwości osiągnięcia exit <- true
w goroutine.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-13 13:54:39
Oto inna opcja.
Kod Konsumenta:
go func() {
stop := false
for {
select {
case item, _ := <-r.queue:
doWork(item)
case <-r.stopping:
stop = true
}
if stop && len(r.queue) == 0 {
break
}
}
}()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 02:52:56