go - qué - comunicacion bidireccional
¿Cuál es el punto de los canales unidireccionales en Go? (3)
Creo que la principal motivación para los canales de solo lectura es evitar la corrupción y los pánicos del canal. Imagina si pudieras escribir en el canal devuelto por el time.After
. time.After
. Esto podría arruinar un montón de código.
Además, los pánicos pueden ocurrir si usted:
- cerrar un canal más de una vez
- escribir en un canal cerrado
Estas operaciones son errores en tiempo de compilación para canales de solo lectura, pero pueden causar desagradables condiciones de carrera cuando múltiples rutinas pueden escribir / cerrar un canal.
Una forma de evitar esto es nunca cerrar los canales y dejar que sean basura. Sin embargo, close
no es solo para la limpieza, sino que realmente tiene uso cuando el canal se extiende:
func consumeAll(c <-chan bool) {
for b := range c {
...
}
}
Si el canal nunca se cierra, este ciclo nunca terminará. Si hay múltiples rutinas que escriben en un canal, hay que llevar a cabo muchas operaciones de contabilidad para decidir cuál cerrará el canal.
Como no puede cerrar un canal de solo lectura, esto hace que sea más fácil escribir el código correcto. Como señaló @jimt en su comentario, no puede convertir un canal de solo lectura en un canal grabable, por lo que tiene la garantía de que solo partes del código con acceso a la versión grabable de un canal pueden cerrarlo / escribir en él.
Editar:
En cuanto a tener múltiples lectores, esto está completamente bien, siempre y cuando lo contabilice. Esto es especialmente útil cuando se usa en un modelo de productor / consumidor. Por ejemplo, supongamos que tiene un servidor TCP que solo acepta conexiones y las escribe en una cola para hilos de trabajo:
func produce(l *net.TCPListener, c chan<- net.Conn) {
for {
conn, _ := l.Accept()
c<-conn
}
}
func consume(c <-chan net.Conn) {
for conn := range c {
// do something with conn
}
}
func main() {
c := make(chan net.Conn, 10)
for i := 0; i < 10; i++ {
go consume(c)
}
addr := net.TCPAddr{net.ParseIP("127.0.0.1"), 3000}
l, _ := net.ListenTCP("tcp", &addr)
produce(l, c)
}
Es probable que el manejo de su conexión tarde más que aceptar una nueva conexión, por lo que desea tener muchos consumidores con un solo productor. Múltiples productores es más difícil (porque necesita coordinar quién cierra el canal) pero puede agregar algún tipo de canal de tipo semáforo al envío del canal.
Estoy aprendiendo Go y hasta ahora estoy muy impresionado con eso. He leído todos los documentos en línea en golang.org y estoy a la mitad de "The Go Programming Language Phrasebook" de Chrisnall. Obtengo el concepto de canales y pienso que serán extremadamente útiles. Sin embargo, debo haberme perdido algo importante en el camino, ya que no puedo ver el punto en los canales unidireccionales.
Si los estoy interpretando correctamente, solo se puede recibir un canal de solo lectura y solo se puede transmitir un canal de solo lectura, entonces, ¿por qué tener un canal que puede enviar y nunca recibir? ¿Pueden ser lanzados de una "dirección" a la otra? Si es así, de nuevo, ¿cuál es el punto si no hay una restricción real? ¿Son nada más que una pista sobre el código del cliente para el propósito del canal?
Los canales Go se modelan en los Procesos secuenciales de comunicación de Hoare, un proceso de álgebra para la concurrencia que se orienta en torno a flujos de eventos entre actores que se comunican (letra ''a'' pequeña). Como tal, los canales tienen una dirección porque tienen un extremo de envío y un de recepción, es decir, un productor de eventos y un consumidor de eventos. Un modelo similar se usa en Occam y Limbo también.
Esto es importante: sería difícil razonar sobre problemas de interbloqueo si un canal de extremo podría reutilizarse arbitrariamente como emisor y receptor en diferentes momentos.
Un canal de solo lectura se puede hacer de solo lectura para quien lo reciba, mientras que el remitente todavía tiene un canal bidireccional en el que puede escribir. Por ejemplo:
func F() <-chan int {
// Create a regular, two-way channel.
c := make(chan int)
go func() {
defer close(c)
// Do stuff
c <- 123
}()
// Returning it, implicitely converts it to read-only,
// as per the function return value.
return c
}
Quien alguna vez llame a F()
, recibe un canal en el que solo puede leer. Esto es más útil para detectar potenciales usos erróneos de un canal en tiempo de compilación. Debido a que los canales de solo lectura / escritura son tipos distintos, el compilador puede usar sus mecanismos de verificación de tipos existentes para garantizar que la persona que llama no intente escribir cosas en un canal en el que no tiene que escribir.