Concurrencia
## Objetivos de Aprendizaje
- Comprender goroutines
- Trabajar con canales
- Dominar operaciones de canal
- Usar sentencias select
- Manejar condiciones de carrera
- Implementar patrones comunes
## Goroutines
### ¿Qué es una Goroutine?
Una goroutine es un hilo ligero gestionado por el runtime de Go.
### Sintaxis Básica
```go
go f(x, y, z) // Inicia una nueva goroutine
```
### Ejemplo
```go
func main() {
go sayHello()
fmt.Println("Hola desde main")
time.Sleep(time.Second) // Esperar goroutine
}
func sayHello() {
fmt.Println("Hola desde goroutine")
}
```
### Función Anónima
```go
go func() {
fmt.Println("Ejecutando en goroutine")
}()
time.Sleep(time.Second)
```
## Canales
### Declaración
```go
ch := make(chan int) // Sin buffer
ch := make(chan int, 10) // Con buffer de capacidad 10
```
### Enviar y Recibir
```go
ch := make(chan int)
// Enviar
ch <- 42
// Recibir
value := <-ch
```
### Operaciones de Canal
```go
ch := make(chan string, 2)
ch <- "Hola" // Enviar
ch <- "Mundo" // Enviar
msg1 := <-ch // Recibir
msg2 := <-ch // Recibir
```
### Cerrar
```go
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
```
## Canales Direccionales
### Especificación
```go
chan T // Puede enviar y recibir
chan<- T // Solo envío
<-chan T // Solo recepción
```
### Casos de Uso
```go
// Productor: solo puede enviar
func producer(ch chan<- int) {
ch <- 42
}
// Consumidor: solo puede recibir
func consumer(ch <-chan int) {
value := <-ch
fmt.Println(value)
}
```
## Sentencia Select
### select Básico
```go
select {
case msg1 := <-ch1:
fmt.Println("Recibido de ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Recibido de ch2:", msg2)
case sendVal := <-ch3:
fmt.Println("Listo para enviar:", sendVal)
default:
fmt.Println("Sin comunicación")
}
```
### Esperar en Múltiples Canales
```go
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
case <-time.After(time.Second):
fmt.Println("Tiempo agotado")
}
```
### Comunicación No Bloqueante
```go
select {
case msg := <-ch:
fmt.Println("Recibido:", msg)
default:
fmt.Println("Sin mensaje listo")
}
```
## Patrones de Canales
### Pipeline
```go
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
for n := range square(square(generate(1, 2, 3, 4, 5))) {
fmt.Println(n)
}
}
```
### Fan-out, Fan-in
```go
func merge(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
for v := range c {
out <- v
}
wg.Done()
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
```
### Worker Pool
```go
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d procesando trabajo %d\n", id, j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
<-results
}
}
```
## Condiciones de Carrera
### Problema
```go
var counter int
func increment() {
counter++ // ¡No es atómico!
}
func main() {
for i := 0; i < 1000; i++ {
go increment()
}
time.Sleep(time.Second)
fmt.Println(counter) // Probablemente no es 1000
}
```
### Solución con Mutex
```go
import "sync"
var (
counter int
mu sync.Mutex
)
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
increment()
wg.Done()
}()
}
wg.Wait()
fmt.Println(counter) // 1000
}
```
### WaitGroup
```go
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
doWork(i)
}()
}
wg.Wait() // Bloquear hasta que todos terminen
```
## Paquete sync
### Mutex
```go
var mu sync.Mutex
func criticalSection() {
mu.Lock()
defer mu.Unlock()
// Código protegido
}
```
### RWMutex
```go
var (
mu sync.RWMutex
data map[string]int
)
func read(key string) int {
mu.RLock()
defer mu.RUnlock()
return data[key]
}
func write(key string, value int) {
mu.Lock()
defer mu.Unlock()
data[key] = value
}
```
### Once
```go
var (
once sync.Once
single *Config
)
func getConfig() *Config {
once.Do(func() {
single = &Config{}
})
return single
}
```
### Map (Concurrente)
```go
var syncMap sync.Map
syncMap.Store("key", "value")
value, ok := syncMap.Load("key")
syncMap.Delete("key")
syncMap.Range(func(k, v interface{}) bool {
fmt.Printf("%s: %s\n", k, v)
return true
})
```
### Pool
```go
pool := sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
buf := pool.Get().([]byte)
defer pool.Put(buf)
```
## Paquete Context
### WithCancel
```go
import "context"
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel()
}()
select {
case <-ctx.Done():
fmt.Println("¡Cancelado!")
case <-time.After(3 * time.Second):
fmt.Println("Tiempo agotado")
}
```
### WithTimeout
```go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-ctx.Done():
fmt.Println("Tiempo agotado")
default:
fmt.Println("Procediendo...")
}
```
### WithValue
```go
ctx := context.WithValue(context.Background(), "user", "alice")
if user, ok := ctx.Value("user").(string); ok {
fmt.Println("Usuario:", user)
}
```
## Errores Comunes
### Enviar a Canal Cerrado
```go
ch := make(chan int)
close(ch)
ch <- 1 // PANIC: envío a canal cerrado
```
### Canal Nil
```go
var ch chan int // canal nil
<-ch // Bloquea por siempre
ch <- 1 // Bloquea por siempre
```
### Deadlock
```go
ch := make(chan int)
ch <- 1 // Bloquea (sin receptor)
<-ch // Nunca llega aquí
```
## Resumen
- Goroutines: hilos ligeros vía palabra clave `go`
- Canales: tuberías tipadas para comunicación
- Canales con buffer: `make(chan T, capacity)`
- Canales sin buffer: `make(chan T)` (envíos bloquean hasta recibir)
- `select`: esperar en múltiples canales
- `sync.Mutex`, `sync.RWMutex` para exclusión mutua
- `sync.WaitGroup` para esperar goroutines
- `sync.Map` para acceso concurrente a map
- `context` para cancelación y tiempos límite
- Nunca cerrar desde el receptor; cerrar solo desde el emisor
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →