2012-06-20 9 views
20

Desidero eseguire una routine di routine su due canali, bloccati quando entrambi i canali sono vuoti. Tuttavia, se entrambi i canali contengono dati, voglio che uno sia svuotato prima che l'altro sia gestito.Priorità nella selezione di selezione Vai soluzione alternativa

Nell'esempio di lavoro in basso desidero scaricare tutto lo out prima che venga gestito exit. Io uso uno-select -statement che non ha alcun ordine di priorità. Come posso aggirare il problema, facendo sì che tutti i 10 valori in uscita vengano gestiti prima dell'uscita?

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") 
} 

risposta

14
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!") 
} 

Il caso predefinito della prima selezione lo rende non bloccante. La selezione esaurirà il canale esterno senza guardare il canale di uscita, ma altrimenti non attenderà. Se il canale di uscita è vuoto, scende immediatamente alla seconda selezione. La seconda selezione sta bloccando. Attenderà i dati su entrambi i canali. Se arriva un'uscita, la gestisce e lascia uscire il ciclo. Se i dati arrivano, torna in cima al loop e torna in modalità drain.

+1

L'idea è molto simile alla mia. Ma è vero, con l'istruzione 'continue', ti libererai della necessità di una bandiera. Inteligente. Bene, questa è probabilmente una buona risposta che posso assumere. Grazie! – ANisus

+2

questo si interromperà all'infinito nella prima istruzione di selezione se il canale di uscita è chiuso. – jorelli

+1

jorelli, abbastanza vero. Se si desidera consentire a goroutini ostili o infestati di chiudere il canale in modo imprevisto, si dovrebbe verificare lo stato ok sulla ricezione. – Sonia

5

Un altro approccio:

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 
$ 
+1

Grazie per il suggerimento! Se ti capisco correttamente, suggerisci di usare solo un canale, chiamando un'uscita chiudendo il canale, interrompendo così l'istruzione 'for range'. È vero, forse è un modo migliore per farlo, ma nel mio caso sto lavorando con due canali. – ANisus

1

ho creato una soluzione piuttosto semplice. Fa quello che voglio, ma se qualcun altro ha una soluzione migliore, per favore fatemelo sapere:

exiting := false 
for !exiting || len(out)>0 { 
    select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
     case <-exit: 
      exiting = true 
      fmt.Println("Exiting") 
    } 
} 

invece di uscire sulla ricezione, segnalo un'uscita, uscendo una volta che ho fatto in modo che nulla è lasciato in chan out .

+1

Funziona ed è bello e compatto, ma usa alcuni trucchi che dovresti cercare di evitare in generale. Le bandiere diventano confuse mentre i programmi diventano più grandi. Sono un po 'come le gotos. Più seriamente, len (chan) può spesso introdurre gare. Va bene in questa situazione, ma in molti casi non è valido prendere una decisione basata su len (chan) perché può cambiare prima di agire. Immagina il caso in cui ottieni len == 0, poi arriva un valore, poi arriva un'uscita e seleziona seleziona l'uscita. Potresti scrollarti di spalle e dire che sono arrivati ​​all'incirca nello stesso periodo, ma in alcuni programmi critici, potrebbe avere importanza. – Sonia

+0

Umm, forse funziona ancora nel caso che ho descritto. Scusa se è un cattivo esempio. Ma comunque, cerco di evitare di usare len nel codice di sincronizzazione. – Sonia

+0

Ciao di nuovo Sonia :). Buon input Sì, nel mio caso non importa molto. Volevo solo svuotare quello che stava uscendo prima di uscire. Comunque, in realtà ho rifatto il codice usando 'per range' e' close (out) '(come suggerito da jmnl). Quindi solo gli eventi fuori piazzati nella pipe del canale che precede la chiusura verrebbero "svuotati". Eviterò di prendere decisioni basate su len (chan) se Nasdaq mi chiedesse mai di fare qualche programma Go per loro;) – ANisus

26

il linguaggio supporta questo in modo nativo e non è necessaria alcuna soluzione alternativa. È molto semplice: il canale di uscita dovrebbe essere visibile solo al produttore. Alla fine, il produttore chiude il canale. Solo quando il canale è vuoto e chiuso il consumatore si licenzia. Ciò è reso possibile dalla lettura del canale come segue:

v, ok := <-c 

questo imposterà ok ad un booleano che indica se il valore v era effettivamente leggere il canale (ok == true), o se v stato impostato sul valore zero del tipo gestito dal canale c perché c è chiuso e vuoto (ok == false). Quando il canale è chiuso e non vuoto, v sarà un valore valido e ok sarà true. Quando il canale è chiuso e vuoto, v sarà il valore zero del tipo gestito dal canale c e ok sarà false, indicando che v è inutile.

Ecco un esempio per illustrare:

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) 
} 

questo è documentato nella sezione "Ricezione Operator" delle specifiche Go: http://golang.org/ref/spec#Receive_operator

+0

Grazie questa è esattamente la soluzione che stavo cercando, e non ha il potenziale bug della condizione di competizione che è nella risposta di Sonia – BrandonAGr

0

Nel mio caso, ho veramente voluto dare la priorità dei dati da un canale su un altro e non solo un segnale di uscita fuori banda.A beneficio di chiunque altro con lo stesso problema credo che questo approccio funziona senza il potenziale condizione di gara:

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

penso la risposta di Sonia è incorrect.This è la mia soluzione, un po 'complicata.

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

C'è un motivo specifico per l'utilizzo di un canale tamponato make(chan int, 10)?

È necessario utilizzare un canale unbuffered vs buffered, che si sta utilizzando.

Basta rimuovere 10, dovrebbe essere solo make(chan int).

In questo modo l'esecuzione nella funzione sender può procedere solo al exit <- true dichiarazione dopo l'ultimo messaggio dal canale out viene rimossa dalla coda da dichiarazione i := <-out. Se questa istruzione non è stata eseguita, non è possibile raggiungere exit <- true nella goroutine.

0

Ecco un'altra opzione. Codice

dei consumatori:

go func() { 
    stop := false 
    for { 
     select { 
     case item, _ := <-r.queue: 
     doWork(item) 
     case <-r.stopping: 
     stop = true 
     } 
     if stop && len(r.queue) == 0 { 
     break 
     } 
    } 
    }() 
Problemi correlati