Comprender cómo funcionan las goroutines, los canales y los apuntadores es fundamental para escribir programas concurrentes y eficientes en Go. A continuación se explican estos tres pilares con ejemplos prácticos extraídos directamente del código trabajado en la sesión.
¿Por qué una goroutine no imprime nada si no usamos canales?
Una goroutine se crea anteponiendo la palabra reservada go antes de la llamada a una función [0:18]. En el ejemplo se define una función llamada doSomething que pone a dormir el proceso durante tres segundos con time.Sleep y luego imprime done.
go
func doSomething() {
time.Sleep(3 * time.Second)
fmt.Println("done")
}
Al invocarla con go doSomething(), la función se ejecuta en una rutina diferente a la de main [0:33]. El problema es que main no espera a que doSomething termine: el programa finaliza antes de que pasen los tres segundos y el mensaje done nunca aparece en pantalla [0:48].
¿Cómo se comunican goroutines mediante canales?
Para resolver este problema se emplea un canal (channel). Un canal permite enviar y recibir valores entre goroutines, actuando como mecanismo de sincronización [1:02].
- Se crea un canal de tipo entero:
c := make(chan int).
- Se modifica
doSomething para que reciba ese canal como parámetro.
- Dentro de la función se envía un valor al canal con el operador
<-: c <- 1 [1:22].
- En
main, la lectura del canal (<-c) bloquea la ejecución hasta que el mensaje llegue [1:32].
go
func doSomething(c chan int) {
time.Sleep(3 * time.Second)
fmt.Println("done")
c <- 1
}
func main() {
c := make(chan int)
go doSomething(c)
<-c
}
Al ejecutar el programa, los tres segundos transcurren y el done se imprime correctamente [1:42]. La lección clave es que crear una goroutine no basta; se necesita un canal u otro mecanismo para coordinar la comunicación entre rutinas [1:55].
¿Qué diferencia hay entre un apuntador y un valor en Go?
Un apuntador (pointer) almacena la dirección de memoria de otra variable en lugar de su valor directo [2:08].
¿Cómo se crean y se leen apuntadores?
Se declara una variable g con valor 25. Para obtener su apuntador se utiliza el operador ampersand (&) [2:18]:
go
g := 25
h := &g
fmt.Println(h) // imprime la dirección de memoria, por ejemplo 0xc0000b4008
Si se imprime h directamente, Go muestra la dirección de memoria donde está alojado g [2:38]. Para acceder al valor real almacenado en esa dirección se usa el operador asterisco (*) [2:50]:
go
fmt.Println(*h) // imprime 25
&g: devuelve la referencia (dirección de memoria) de g.
*h: devuelve el valor almacenado en la dirección a la que apunta h.
Confundir estos dos operadores puede provocar errores difíciles de rastrear, por lo que conviene tener claro cuándo se trabaja con la dirección y cuándo con el valor [3:08].
¿Qué sintaxis esencial conviene recordar?
Estos son los elementos revisados y su propósito:
go + función: lanza una goroutine.
make(chan tipo): crea un canal del tipo indicado.
canal <- valor: envía un dato al canal.
<-canal: recibe un dato del canal y bloquea hasta obtenerlo.
&variable: obtiene el apuntador (dirección de memoria).
*apuntador: accede al valor almacenado en la dirección.
Dominar estos fundamentos prepara el camino para temas más avanzados como la programación orientada a objetos en Go, que es justamente lo que sigue en el curso [3:30]. Si alguno de estos conceptos te generó dudas, comparte tu experiencia en los comentarios.