2012-06-20 8 views
20

Deseo tener una rutina de escucha en dos canales, bloqueada cuando se agotan ambos canales. Sin embargo, si ambos canales contienen datos, quiero que uno sea drenado antes de que se maneje el otro.Prioridad en Go, seleccione la solución de la solución

En el siguiente ejemplo de trabajo, deseo que todos los out se vacíen antes de manipular exit. Utilizo una declaración select-que no tiene ningún orden de prioridad. ¿Cómo puedo solucionar el problema, haciendo que los 10 valores positivos se manejen antes de la salida?

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

Respuesta

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

El caso predeterminado de la primera selección lo hace no bloqueante. La selección drenará el canal de salida sin mirar el canal de salida, pero de lo contrario no esperará. Si el canal de salida está vacío, inmediatamente cae a la segunda selección. La segunda selección es bloqueo. Esperará datos en cualquier canal. Si llega una salida, la maneja y permite que el bucle salga. Si se reciben datos, vuelve a la parte superior del ciclo y regresa al modo de drenaje.

+1

La idea es muy similar a la mía. Pero es verdad, con la declaración 'continue', te deshaces de la necesidad de una bandera. Inteligente. Bueno, esta es probablemente la mejor respuesta que puedo asumir. ¡Gracias! – ANisus

+2

esto se repetirá infinitamente en la primera instrucción de selección si el canal de salida está cerrado. – jorelli

+1

jorelli, bastante cierto. Si desea permitir que los goroutines hostiles o defectuosos cierren el canal de forma inesperada, debe verificar el estado correcto en la recepción. – Sonia

5

Otro enfoque:

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

Gracias por la sugerencia! Si te entiendo correctamente, sugieres usar solo un canal, llamando a una salida cerrando el canal, rompiendo así la declaración 'for range'. Es cierto que tal vez sea una mejor manera de hacerlo, pero en mi caso estoy trabajando con dos canales. – ANisus

1

He creado una solución bastante simple. Se hace lo que quiero, pero si alguien tiene una mejor solución, por favor hágamelo saber:

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

en lugar de salir a la recepción, me BANDERA una salida, la salida una vez que me he asegurado de que no queda nada en chan out .

+1

Esto funciona y es agradable y compacto, pero utiliza algunos trucos que debe intentar evitar en general. Las banderas se vuelven confusas a medida que los programas crecen. Son algo así como gotos. Más en serio, Len (chan) a menudo puede introducir carreras. Se ve bien en esta situación, pero en muchos casos no es válido tomar una decisión basada en len (chan) porque puede cambiar antes de tomar medidas. Imagine el caso en el que obtiene len == 0, luego llega un valor, luego llega una salida y selecciona la salida. Puede encogerse de hombros y decir que llegaron aproximadamente al mismo tiempo, pero en algunos programas críticos, podría importar. – Sonia

+0

Umm, tal vez todavía funciona en el caso que describí. Lo siento si es un mal ejemplo. Pero de todos modos, trato de evitar usar len en el código de sincronización. – Sonia

+0

Hola otra vez Sonia :). Buena entrada. Sí, en mi caso no importa mucho. Solo quería limpiar lo que estaba saliendo antes de salir. Sin embargo, en realidad rehice el código usando 'for range' y' close (out) 'en su lugar (como lo sugiere jmnl). Entonces, solo los eventos de salida colocados en la tubería del canal que preceden al cierre se "purgarán". Evitaré tomar decisiones basadas en len (chan) si el Nasdaq alguna vez me pide que haga algún programa de Go para ellos;) – ANisus

26

el idioma es compatible de forma nativa y no se requiere ninguna solución. Es muy simple: el canal de salida solo debe ser visible para el productor. Al salir, el productor cierra el canal. Solo cuando el canal está vacío y cerrado, el consumidor se da por vencido. Esto se hace posible mediante la lectura de la canal como sigue:

v, ok := <-c 

Esto establecerá ok a un valor booleano que indica si o no el valor v se leyó realmente fuera de la canal (ok == true), o si v se fijó a la valor cero del tipo manejado por el canal c porque c está cerrado y vacío (ok == false). Cuando el canal está cerrado y no está vacío, v tendrá un valor válido y ok será true. Cuando el canal está cerrado y vacío, v será el valor cero del tipo manejado por el canal c, y ok será false, lo que indica que v es inútil.

Aquí se muestra un ejemplo para ilustrar:

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

esto está documentado en la sección "Recibir del operador" de la especificación ir: http://golang.org/ref/spec#Receive_operator

+0

Gracias, esta es exactamente la solución que estaba buscando, y no tiene el potencial error de condición de carrera que está en la respuesta de Sonia – BrandonAGr

0

En mi caso, tenía muchas ganas de dar prioridad a los datos de una canal sobre otro, y no solo tiene una señal de salida fuera de banda.Para el beneficio de cualquier otra persona con el mismo problema Creo que este enfoque funciona sin la condición de carrera potencial:

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

creo que la respuesta de Sonia es incorrect.This es mi solución, un poco complicar bits.

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

¿Hay alguna razón específica para el uso de un canal tamponada make(chan int, 10)?

Necesita usar un canal sin buffer vs buffer, que está utilizando.

Simplemente elimine 10, debería ser solo make(chan int).

esta forma de ejecución de la función sender sólo puede proceder a la declaración exit <- true después del último mensaje del canal de out se quita de la cola por la declaración i := <-out. Si esa declaración no se ha ejecutado, no hay forma de que se pueda alcanzar el exit <- true en la rutina.

0

Aquí hay otra opción. Código

Consumidor:

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