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")
}
Author: Sonia, 2012-06-20

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.

 17
Author: Sonia,
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

 30
Author: jorelli,
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
$ 
 5
Author: zzzz,
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.

 1
Author: ANisus,
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
            }
        }
    }

}
 0
Author: monoi,
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!")
}
 0
Author: 海牙客移库,
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.

 0
Author: George Polevoy,
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
      }
    }
  }()
 0
Author: delrox,
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