Go: Concurrencia y paralelismo
Golang ofrece herramientas para la concurrencia y el paralelismo. Descubre cómo utilizar goroutines, canales y otros mecanismos para aprovechar al máximo el procesamiento concurrente en Go.
Aprende Go GRATIS y certifícateLa concurrencia y el paralelismo son elementos fundamentales en la programación moderna, y Golang (Go) se destaca por ofrecer soporte nativo y eficiente para estos paradigmas. En este módulo, exploraremos cómo Go maneja la concurrencia y el paralelismo, y cómo puedes implementar estas características en tus proyectos para mejorar el rendimiento y la eficiencia.
Introducción a la concurrencia en Go
La concurrencia es la capacidad de gestionar múltiples tareas al mismo tiempo, permitiendo que un programa maneje varias operaciones de forma simultánea. En Go, la concurrencia es una característica central del lenguaje, facilitada a través de primitivas integradas como las goroutines y los canales.
Goroutines
Las goroutines son funciones o métodos que se ejecutan de manera concurrente con otras goroutines en el mismo espacio de direcciones. Son ligeras y gestionadas por el runtime de Go, lo que permite crear miles de goroutines sin un impacto significativo en los recursos del sistema.
Sintaxis básica
Para iniciar una goroutine, simplemente precede una llamada a función con la palabra clave go
:
func main() {
go sayHello()
fmt.Println("Este mensaje puede aparecer antes que 'Hola'")
}
func sayHello() {
fmt.Println("Hola")
}
En este ejemplo, sayHello()
se ejecuta concurrentemente con main()
. Dado que main()
puede terminar antes de que sayHello()
se ejecute, es posible que no veamos el mensaje "Hola" a menos que sincronizamos las goroutines.
Canales
Los canales en Go permiten la comunicación y sincronización entre goroutines. Actúan como conductos donde las goroutines pueden enviar y recibir valores de tipos específicos.
Creación de canales
Puedes crear un canal utilizando la función make
:
ch := make(chan int)
Envío y recepción de datos
- Envío: Utiliza el operador
<-
para enviar datos a un canal.
ch <- valor
- Recepción: También utilizas
<-
para recibir datos.
valor := <-ch
Ejemplo de uso de canales
func main() {
ch := make(chan string)
go greet(ch)
message := <-ch
fmt.Println(message)
}
func greet(ch chan string) {
ch <- "Hola desde la goroutine"
}
En este ejemplo, la función greet
envía un mensaje a través del canal ch
, y main
lo recibe y lo imprime.
Paralelismo en Go
El paralelismo implica ejecutar múltiples operaciones al mismo tiempo en sistemas con múltiples núcleos o procesadores. Mientras que la concurrencia es sobre gestionar muchas tareas a la vez, el paralelismo es sobre ejecutarlas simultáneamente.
Configuración del paralelismo
Go permite especificar el número de núcleos utilizados a través de la función runtime.GOMAXPROCS
. Por defecto, Go utiliza todos los núcleos disponibles.
import "runtime"
func main() {
runtime.GOMAXPROCS(4) // Utilizar 4 núcleos
// Código concurrente aquí
}
Patrones comunes de concurrencia
Select
La sentencia select
permite esperar en múltiples operaciones de envío o recepción en canales.
Ejemplo de select
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
ch1 <- "Mensaje del canal 1"
}()
go func() {
ch2 <- "Mensaje del canal 2"
}()
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
En este ejemplo, select
espera a que uno de los canales reciba un mensaje y luego ejecuta el caso correspondiente.
WaitGroup
El paquete sync
ofrece el tipo WaitGroup
para esperar a que un conjunto de goroutines terminen su ejecución.
Uso de WaitGroup
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("Goroutine 1 terminada")
}()
go func() {
defer wg.Done()
fmt.Println("Goroutine 2 terminada")
}()
wg.Wait()
fmt.Println("Todas las goroutines han terminado")
}
En este ejemplo, WaitGroup
espera a que las dos goroutines llamen a Done
antes de continuar.
Contextos en Go
El paquete context
proporciona funciones para controlar la vida de las goroutines. Útil para operaciones cancelables y con tiempo límite.
Creación de un contexto con cancelación
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
// Simular trabajo
time.Sleep(2 * time.Second)
cancel()
}()
select {
case <-ctx.Done():
fmt.Println("Operación cancelada")
}
}
Prácticas recomendadas
Evitar bloqueos: Asegúrate de que las goroutines no quedan bloqueadas esperando en canales sin receptores.
Cerrar canales correctamente: Cuando ya no necesites un canal, ciérralo para evitar fugas de recursos.
close(ch)
- Uso de mutexes: Para secciones críticas donde el acceso concurrente puede causar condiciones de carrera, utiliza
sync.Mutex
.
var mu sync.Mutex
mu.Lock()
// Sección crítica
mu.Unlock()
Ejemplo completo: procesamiento concurrente
A continuación, un ejemplo que demuestra el uso de goroutines y canales para procesar datos concurrentemente.
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Trabajador %d procesando trabajo %d\n", id, j)
time.Sleep(time.Second * time.Duration(rand.Intn(3)))
results <- j * 2
}
}
func main() {
rand.Seed(time.Now().Unix())
jobs := make(chan int, 100)
results := make(chan int, 100)
var wg sync.WaitGroup
// Iniciar trabajadores
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Enviar trabajos
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
// Esperar a que los trabajadores terminen
wg.Wait()
close(results)
// Recoger resultados
for result := range results {
fmt.Printf("Resultado: %d\n", result)
}
}
Este programa crea tres trabajadores que procesan cinco trabajos. Cada trabajador procesa un trabajo y envía el resultado al canal results
.
Conclusión
La concurrencia y el paralelismo en Golang permiten desarrollar aplicaciones eficientes y escalables. Al dominar goroutines, canales y otras herramientas concurrentes, puedes aprovechar al máximo los recursos del sistema y mejorar el rendimiento de tus aplicaciones.
Lecciones de este módulo de Go
Lecciones de programación del módulo Concurrencia y paralelismo del curso de Go.
Ejercicios de programación en este módulo de Go
Evalúa tus conocimientos en Concurrencia y paralelismo con ejercicios de programación Concurrencia y paralelismo de tipo Test, Puzzle, Código y Proyecto con VSCode.