Aquí hay un ejemplo de una función que recibe de un canal en un bucle, pero se da por vencida después de cierto tiempo:
Versión común
func process(channel <-chan Result, timeout time.Duration)
timer := time.NewTimer(timeout)
defer timer.Stop() for {
select {
case value, ok := <-channel:
if !ok {
return
}
doSomething(value)
case <-timer.C:
return
}
}
}
- ¿Cuáles son las habilidades matemáticas mínimas para un ingeniero de software?
- Mi IPC es bajo, alrededor de 6.5. ¿Cómo puedo obtener un buen trabajo de ingeniería de software?
- Cómo encontrar un mentor que me guíe como desarrollador de software
- ¿Qué metodologías y técnicas de desarrollo de software realmente ayudan a las nuevas empresas de software y cómo?
- ¿Cuál es un buen teclado mecánico que recomiendas para desarrolladores?
func process(channel <-chan Result, timeout time.Duration)
timer := time.NewTimer(timeout)
defer timer.Stop() for {
select {
case value, ok := <-channel:
if !ok {
return
}
doSomething(value)
case <-timer.C:
return
}
}
}
Algunas notas sobre por qué funciona de la manera que lo hace:
- Observe que el canal es de tipo
, lo que significa que solo se puede usar para operaciones de recepción. Esta es una buena práctica al pasar canales a un receptor, de forma análoga al uso de<-chan ...
const
en C ++. Evita algunos errores simples como llamar aclose()
en el lado del receptor. Solo el lado del remitente debe cerrar un canal, porque necesita saber que ya no debe enviarlo, o de lo contrario entrará en pánico. - Está escrito como una función autónoma, porque aclara la lógica para romper tanto la
select
como el bucle. Podría usar etiquetas para hacer esto, pero casi siempre es el caso de que un bucle que es lo suficientemente complejo como para necesitar etiquetas es lo suficientemente complejo como para necesitar encapsularse en una función. - El
es opcional, pero es una buena práctica siempre que use temporizadores. Significa que si la función regresa antes de que expire el temporizador, ya no pagará el costo de los recursos del temporizador. De lo contrario, la rutina del temporizador seguirá existiendo después de que regrese la función, lo que puede ser un problema si está llamando adefer timer.Stop()
process()
en un ciclo cerrado.
Versión Ultra-pedante
Lo anterior es un patrón común que verá, pero técnicamente es sutilmente defectuoso. Si el canal siempre está listo para recibir, es posible (aunque exponencialmente improbable) que el ciclo nunca se agote. Esto se debe a que si hay varios casos en una select
listos, el tiempo de ejecución de Go elegirá uno de ellos de forma seudoaleatoria .
Esto es una gran sorpresa para muchos, pero dado que la probabilidad de continuar es de 1/2 en cada iteración, en la práctica solo verá una o dos iteraciones adicionales, lo que nunca notará si su condición de detención se basa en el tiempo .
Sin embargo, generalmente agrego una segunda verificación de tiempo de espera en la parte superior del ciclo para evitar esto, porque creo en escribir código que haga lo que pretendía de manera determinista, en lugar de solo con una probabilidad muy muy muy alta.
func process(channel <-chan Result, timeout time.Duration)
timer := time.NewTimer(timeout)
defer timer.Stop()
para {
seleccione {
case <-timer.C:
regreso
defecto:
}
seleccione {
valor del caso, ok: = <-canal:
si! ok {
regreso
}
hacer algo (valor)
case <-timer.C:
regreso
}
}
}
El default:
case en la primera select
convierte en una comprobación sin bloqueo. Si el temporizador no ha expirado, simplemente fallará de inmediato.