new - golang concurrency
Golang: estructura anónima y estructura vacía (5)
Los literales compuestos construyen valores para estructuras, matrices, cortes y mapas y crean un nuevo valor cada vez que se evalúan. Consisten en el tipo de valor seguido de una lista de elementos compuestos enlazados a llaves. Un elemento puede ser una sola expresión o un par clave-valor.
struct{}{}
es un literal compuesto de tipo struct{}
, el tipo del valor seguido de una lista de elementos compuestos enlazados a llaves.
for _ = range langs { <-done }
está esperando hasta que todos los goroutines de todos los langs
hayan enviado mensajes langs
.
http://play.golang.org/p/vhaKi5uVmm
package main
import "fmt"
var battle = make(chan string)
func warrior(name string, done chan struct{}) {
select {
case opponent := <-battle:
fmt.Printf("%s beat %s/n", name, opponent)
case battle <- name:
// I lost :-(
}
done <- struct{}{}
}
func main() {
done := make(chan struct{})
langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
for _, l := range langs { go warrior(l, done) }
for _ = range langs { <-done }
}
[Primera pregunta]
done <- struct{}{}
¿Cómo y por qué necesitamos esta estructura de aspecto extraño? ¿Es una estructura vacía o una estructura anónima? Lo busqué en Google pero no pude encontrar la respuesta correcta o la documentación para explicar sobre esto.
La fuente original es de la charla de Andrew Gerrand http://nf.wh3rd.net/10things/#10
aquí
make(chan struct{})
hecho es un canal de tipo struct {}
Así que lo intenté con
done <- struct{}
Pero no está funcionando. ¿Por qué necesito un paréntesis extra para esta línea?
done <- struct{}{}
[Segunda pregunta]
for _ = range langs { <-done }
¿Por qué necesito esta línea? Sé que esta línea es necesaria porque sin esta línea, no hay salida. Pero ¿por qué y qué hace esta línea? ¿Y qué lo hace necesario en este código? Sé que <-done
es para recibir los valores del canal hecho y descartar los valores recibidos. Pero ¿por qué necesito hacer esto?
¡Gracias!
struct{}
es un tipo (en particular, una estructura sin miembros). Si tiene un tipoFoo
, puede crear un valor de ese tipo en una expresión conFoo{field values, ...}
. Juntando esto,struct{}{}
es un valor del tipostruct{}
, que es lo que espera el canal.La función
main
engendrawarrior
goroutines, que escribirán al canaldone
cuando hayan terminado. Las últimas lecturas de bloque de este canal, asegurando quemain
no volverá hasta que todos los goroutines hayan terminado. Esto es importante porque el programa se cerrará cuando finalice la funciónmain
, independientemente de si hay otros goroutines en ejecución.
Buena pregunta,
El punto central del canal de estructura en este escenario es simplemente señalar la conclusión de que algo útil ha sucedido. El tipo de canal realmente no importa, podría haber usado un int o un bool para lograr el mismo efecto. Lo importante es que su código se ejecuta de manera sincronizada en el que está realizando la contabilidad necesaria para señalar y avanzar en los puntos clave.
Estoy de acuerdo en que la sintaxis de la struct{}{}
parece extraña al principio porque en este ejemplo él declara una estructura y la crea en línea, por lo tanto, el segundo conjunto de corchetes.
Si tuvieras un objeto preexistente como:
type Book struct{
}
Podría crearlo así: b := Book{}
, solo necesita un conjunto de paréntesis porque la estructura del Libro ya ha sido declarada.
Tenga en cuenta que un aspecto interesante del uso de struct {} para el tipo enviado a un canal (a diferencia de int o bool), es que el tamaño de una estructura vacía es ... ¡0!
Vea el artículo reciente " La estructura vacía " (marzo de 2014) de Dave Cheney .
Puede crear tantas struct{}
como desee ( struct{}{}
) para enviarlas a su canal: su memoria no se verá afectada.
Pero puede usarlo para señalizar entre rutinas de ir, como se ilustra en " Curious Channels ".
Y conservas todas las otras ventajas vinculadas a una estructura:
- puede definir métodos en él (ese tipo puede ser un receptor de método)
- puede implementar una interfaz (con dichos métodos que acaba de definir en su estructura vacía)
- como un singleton
en Go puede usar una estructura vacía y almacenar todos sus datos en variables globales. Solo habrá una instancia del tipo, ya que todas las estructuras vacías son intercambiables.
Ver, por ejemplo, la var global errServerKeyExchange
en el archivo donde se define la estructura vacía rsaKeyAgreement
.
done
canal done
se usa para recibir notificaciones del método de warrior
que indica que el trabajador ha terminado el procesamiento. Entonces el canal puede ser cualquier cosa, por ejemplo:
func warrior(name string, done chan bool) {
select {
case opponent := <-battle:
fmt.Printf("%s beat %s/n", name, opponent)
case battle <- name:
// I lost :-(
}
done <- true
}
func main() {
done := make(chan bool)
langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
for _, l := range langs { go warrior(l, done) }
for _ = range langs { <-done }
}
Declaramos done := make(chan bool)
como un canal que recibe un valor bool, y enviamos true
al final de warrior
. ¡Esto funciona! También puede definir el canal done
para cualquier otro tipo, no importará.
1. Entonces, ¿qué pasa con lo extraño done <- struct{}{}
?
Es solo otro tipo que se pasará al canal. Esta es una estructura vacía, si está familiarizado con lo siguiente:
type User struct {
Name string
Email string
}
struct{}
no hace ninguna diferencia excepto que no contiene campos, y struct{}{}
es solo una instancia de él. La mejor característica es que no cuesta espacio de memoria!
2. para el uso de bucle
Creamos 6 goroutines para ejecutar en segundo plano con esta línea:
for _, l := range langs { go warrior(l, done) }
Usamos for _ = range langs { <-done }
, porque la goroutine principal (donde se ejecuta la función principal) no espera que los goroutins terminen.
Si no incluimos la última línea de la línea, es probable que no veamos resultados (porque las goroutinas principales se cierran antes de que cualquier goroutines infantil ejecute el código fmt.Printf
, y cuando la goroutine principal se cierre, todas las goroutines infantiles abandonarán el sistema y no tendrán ninguna oportunidad de correr de todos modos).
Así que esperamos a que todos los goroutines terminen (se ejecuta hasta el final y envían un mensaje al canal done
), luego sale. done
canal done
aquí es un canal bloqueado, lo que significa que <-done
se bloqueará aquí hasta que se reciba un mensaje del canal.
Tenemos 6 goroutines en segundo plano, y usamos para bucle, esperamos hasta que todos los goroutines envíen un mensaje que significa que terminó de ejecutarse (porque el done <-struct{}{}
está al final de la función).