Lo esencial
- ¿Qué es un hilo?
Un hilo es una ruta de ejecución dentro de un proceso. - ¿Por qué multihilo?
La idea es lograr el paralelismo dividiendo un proceso en múltiples subprocesos. Por ejemplo, en un navegador, varias pestañas pueden ser diferentes hilos. - Proceso e hilo
La principal diferencia es que los hilos dentro del mismo proceso se ejecutan en un espacio de memoria compartida, mientras que los procesos se ejecutan en espacios de memoria separados.
Goroutine
A goroutine
es un hilo ligero gestionado por el tiempo de ejecución de Go.
go f(x, y, z) // starts a new goroutine by running f(x, y, z)
La evaluación de f
, x
, y
, z
sucede en el goroutine actual y la ejecución de los f
que sucede en el nuevo goroutine.
Las gorutinas se ejecutan en el mismo espacio de direcciones, por lo que el acceso a la memoria compartida debe estar sincronizado. El sync
paquete proporciona primitivas útiles, aunque no las necesitará mucho en Go, ya que hay otras primitivas.
- Caso 1
func display(str string) {
time.Sleep(2 * time.Second)
fmt.Println(str)
}
func main() {
display("NORMAL")
go display("GOROUTINE")
}
Resultado: NORMAL
2. Caso 2
func display(str string) {
time.Sleep(2 * time.Second)
fmt.Println(str)
}
func main() {
go display("GOROUTINE")
display("NORMAL")
}
Resultado: GOROUTINE\nNORMAL
3. Caso 3
func display(str string) {
fmt.Println(str)
}
func main() {
go display("GOROUTINE")
display("NORMAL")
}
Producción: NORMAL
Canales
Los canales son un conducto mecanografiado a través del cual puede enviar y recibir con el operador del canal <-
.
ch <- v // send v to channel ch
v := <- ch // receive from ch, and assign value to v
// like map and slice, channel must be created before use
ch := make(chan int)
Ejemplo de dos gorutinas que resuelven una tarea juntas:
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{
7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
Canales en búfer
Los canales se pueden almacenar en búfer .
Proporcione la longitud del búfer como segundo argumento make
para inicializar un canal en búfer:
ch := make(chan int, 100)
Envía a un bloque de canal almacenado en búfer solo cuando el búfer está lleno. Recibe bloque cuando el búfer está vacío.
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
ch <- 3 // deadlock, all goroutines are asleep
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Alcance y cierre
Un remitente puede cerrar un canal:
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
Los receptores pueden probar si un canal está cerrado:
v, ok := <-ch
El bucle for i := range ch
recibe valores del canal ch
repetidamente hasta que se cierra.
Enviar en un canal cerrado provocará un panic
.
Los canales no son como archivos; normalmente no es necesario cerrarlos. El cierre solo es necesario cuando se le debe decir al receptor que no hay más valores, como para terminar un range
bucle.
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
Seleccione
La select
declaración permite que una goroutine espere en múltiples operaciones de comunicación (como c <- x).
A select
bloquea hasta que uno de sus casos pueda ejecutarse, luego ejecuta ese caso. Elige uno al azar si hay varios listos.
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Sintaxis de la función goroutine anónima:
go func() {
// function body
}()
El default
caso en a select
se ejecuta si no hay otro caso listo.
sincronizar Mutex
// SafeCounter is safe to use concurrently.
type SafeCounter struct {
mu sync.Mutex // make sure only one goroutine can access a variable at a time to avoid conflicts
v map[string]int
}
// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
// define a block of code to be executed in mutual exclusion by surrounding it with a call to Lock and Unlock
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
c.v[key]++
c.mu.Unlock()
}
// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
// Lock so only one goroutine at a time can access the map c.v.
defer c.mu.Unlock() // use defer to ensure the mutex will be unlocked
return c.v[key]
}
func main() {
c := SafeCounter{
v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
fmt.Println(c.Value("somekey"))
}