concurrency - close - Entender los goroutines
goroutine pool (4)
De acuerdo con this y this , algunas llamadas no pueden invocarse durante un límite de CPU (si el Goroutine nunca cede al planificador). Esto puede hacer que otros Goroutines se cuelguen si necesitan bloquear el hilo principal (tal es el caso del syscall write()
utilizado por fmt.Println()
)
La solución que encontré implicó llamar a runtime.Gosched()
en su subproceso enlazado a la CPU para devolver al planificador, de la siguiente manera:
package main
import (
"fmt"
"runtime"
)
var x = 1
func inc_x() {
for {
x += 1
runtime.Gosched()
}
}
func main() {
go inc_x()
for {
fmt.Println(x)
}
}
Debido a que solo está realizando una operación en el Goroutine, runtime.Gosched()
se está llamando con mucha frecuencia. Llamar a tiempo de runtime.GOMAXPROCS(2)
en init es más rápido en un orden de magnitud, pero sería muy inseguro si no hiciera algo más complicado que incrementar un número (por ejemplo, tratar con matrices, estructuras, mapas, etc.).
En ese caso, la mejor práctica sería utilizar un canal para administrar el acceso compartido a un recurso.
Actualización: a partir de Go 1.2, cualquier llamada de función no en línea puede invocar al programador.
Estoy tratando de entender la concurrencia en Go. En particular, escribí este programa inseguro de subprocesos:
package main
import "fmt"
var x = 1
func inc_x() { //test
for {
x += 1
}
}
func main() {
go inc_x()
for {
fmt.Println(x)
}
}
Reconozco que debería usar canales para evitar condiciones de carrera con x
, pero ese no es el punto aquí. El programa imprime 1
y luego parece que se repite para siempre (sin imprimir nada más). Esperaría que imprima una lista infinita de números, posiblemente omitiendo algunos y repitiendo otros debido a la condición de carrera (o peor, imprimiendo el número mientras se actualiza en inc_x
).
Mi pregunta es: ¿por qué el programa solo imprime una línea?
Solo para ser claro: no estoy usando canales a propósito para este ejemplo de juguete.
Es una interacción de dos cosas. Uno, de forma predeterminada, Go solo usa un solo núcleo, y dos, Go debe programar goroutines cooperativamente. Su función inc_x no cede, por lo que monopoliza el núcleo único que se está utilizando. Aliviar cualquiera de estas condiciones dará lugar a la producción que espera.
Decir "core" es un poco brillante. En realidad, Go puede usar múltiples núcleos detrás de la escena, pero usa una variable llamada GOMAXPROCS para determinar el número de subprocesos para programar sus rutinas que están realizando tareas que no son del sistema. Como se explica en las FAQ y Effective Go, el valor predeterminado es 1, pero puede establecerse más alto con una variable de entorno o una función de tiempo de ejecución. Es probable que esto le proporcione el resultado esperado, pero solo si su procesador tiene múltiples núcleos.
Independientemente de los núcleos y GOMAXPROCS, puede darle la oportunidad al planificador de rutina en el tiempo de ejecución de hacer su trabajo. El planificador no puede apropiarse de una rutina de ejecución pero debe esperar a que regrese al tiempo de ejecución y solicite algún servicio, como IO, time.Sleep () o runtime.Gosched (). Agregar algo como esto en inc_x produce el resultado esperado. El administrador de goroutine main () ya está solicitando un servicio con fmt.Println, por lo que con las dos rutinas que ahora periódicamente ceden al tiempo de ejecución, puede hacer algún tipo de programación justa.
Hay algunas cosas a tener en cuenta acerca de los goroutinos de Go.
- No son hilos en el sentido de hilos de Java o C ++.
- Son más como verdes.
- El tiempo de ejecución go multiplexa las rutinas en los hilos del sistema
- El número de subprocesos del sistema está controlado por una variable de entorno GOMAXPROCS y por defecto, el valor predeterminado es 1, creo. Esto puede cambiar en el futuro.
- La forma en que los goroutines ceden a su hilo actual está controlada por varias construcciones diferentes.
- La instrucción select puede devolver el control al hilo.
- enviar en un canal puede devolver el control al hilo.
- Hacer operaciones IO puede devolver el control al hilo.
- runtime.Gosched () cede explícitamente el control al hilo.
El comportamiento que está viendo se debe a que la función principal nunca cede al hilo y, en cambio, está involucrada en un ciclo ocupado y, dado que solo hay un hilo, el ciclo principal no tiene lugar para ejecutarse.
No estoy seguro, pero creo que inc_x
está acaparando la CPU. Como no hay IO, no libera el control.
Encontré dos cosas que resolvieron eso. Una fue invocar el tiempo de runtime.GOMAXPROCS(2)
al comienzo del programa y luego funcionará, ya que ahora hay dos hilos que sirven de gorouts. El otro es insertar time.Sleep(1)
después de incrementar x
.