Nota:
A partir de Go 1.5, GOMAXPROCS se establece en el número de núcleos del hardware: golang.org/doc/go1.5#runtime , debajo de la respuesta original antes de 1.5.
Cuando ejecuta el programa Go sin especificar la variable de entorno GOMAXPROCS, las rutinas Go gor están programadas para su ejecución en un solo hilo del sistema operativo. Sin embargo, para hacer que el programa parezca ser multiproceso (para eso son las goroutines, ¿no?), El planificador de Go a veces debe cambiar el contexto de ejecución, por lo que cada goroutine podría hacer su trabajo.
Como dije, cuando no se especifica la variable GOMAXPROCS, el tiempo de ejecución de Go solo puede usar un hilo, por lo que es imposible cambiar los contextos de ejecución mientras goroutine está realizando algún trabajo convencional, como cálculos o incluso IO (que se asigna a funciones C simples ). El contexto se puede cambiar solo cuando se utilizan primitivas de concurrencia de Go, por ejemplo, cuando enciende varios canales, o (este es su caso) cuando le dice explícitamente al programador que cambie los contextos; para esto es runtime.Gosched
.
Entonces, en resumen, cuando el contexto de ejecución en una goroutine llega a la Gosched
llamada, el planificador recibe instrucciones para cambiar la ejecución a otra goroutine. En su caso, hay dos goroutines, main (que representa el hilo 'principal' del programa) y adicional, con la que ha creado go say
. Si elimina la Gosched
llamada, el contexto de ejecución nunca se transferirá de la primera goroutine a la segunda, por lo tanto, no habrá 'mundo' para usted. Cuando Gosched
está presente, el planificador transfiere la ejecución en cada iteración de bucle de la primera goroutine a la segunda y viceversa, por lo que tiene 'hola' y 'mundo' intercalados.
Para su información, esto se llama 'multitarea cooperativa': las gorutinas deben ceder explícitamente el control a otras gorutinas. El enfoque utilizado en la mayoría de los sistemas operativos contemporáneos se denomina 'multitarea preventiva': los hilos de ejecución no se preocupan por la transferencia de control; en su lugar, el planificador cambia los contextos de ejecución de forma transparente. El enfoque cooperativo se usa con frecuencia para implementar 'subprocesos verdes', es decir, corrutinas lógicas simultáneas que no se asignan 1: 1 a los subprocesos del sistema operativo; así es como se implementan el tiempo de ejecución de Go y sus goroutines.
Actualizar
Mencioné la variable de entorno GOMAXPROCS pero no expliqué qué es. Es hora de arreglar esto.
Cuando esta variable se establece en un número positivo N
, el tiempo de ejecución de Go podrá crear hasta N
subprocesos nativos, en los que se programarán todos los subprocesos verdes. Hilo nativo un tipo de hilo que es creado por el sistema operativo (hilos de Windows, pthreads, etc.). Esto significa que si N
es mayor que 1, es posible que goroutines se programen para ejecutarse en diferentes subprocesos nativos y, en consecuencia, se ejecuten en paralelo (al menos, hasta las capacidades de su computadora: si su sistema está basado en un procesador multinúcleo, Es probable que estos subprocesos sean verdaderamente paralelos; si su procesador tiene un solo núcleo, entonces la multitarea preventiva implementada en subprocesos del sistema operativo creará una visibilidad de ejecución paralela).
Es posible configurar la variable GOMAXPROCS usando la runtime.GOMAXPROCS()
función en lugar de preestablecer la variable de entorno. Use algo como esto en su programa en lugar del actual main
:
func main() {
runtime.GOMAXPROCS(2)
go say("world")
say("hello")
}
En este caso puedes observar resultados interesantes. Es posible que obtenga las líneas 'hola' y 'mundo' impresas intercaladas de manera desigual, p. Ej.
hello
hello
world
hello
world
world
...
Esto puede suceder si las gorutinas están programadas para separar los subprocesos del sistema operativo. De hecho, así es como funciona la multitarea preventiva (o el procesamiento paralelo en el caso de sistemas multinúcleo): los hilos son paralelos y su salida combinada es indeterminista. Por cierto, puede dejar o eliminar la Gosched
llamada, parece no tener ningún efecto cuando GOMAXPROCS es mayor que 1.
Lo siguiente es lo que obtuve en varias ejecuciones del programa con runtime.GOMAXPROCS
call.
hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
Mira, a veces la salida es bonita, a veces no. Indeterminismo en acción :)
Otra actualización
Parece que en las versiones más recientes del compilador Go, el tiempo de ejecución de Go obliga a las goroutines a ceder no solo en el uso de primitivas de concurrencia, sino también en las llamadas al sistema operativo. Esto significa que el contexto de ejecución se puede cambiar entre goroutines también en llamadas a funciones IO. En consecuencia, en los compiladores de Go recientes es posible observar un comportamiento indeterminista incluso cuando GOMAXPROCS no está configurado o configurado en 1.
Gosched()
es necesario o no depende de su programa, no depende delGOMAXPROCS
valor. La eficiencia de la multitarea preventiva sobre la cooperativa también depende de su programa. Si su programa está vinculado a E / S, entonces la multitarea cooperativa con E / S asíncrona probablemente será más eficiente (es decir, tendrá más rendimiento) que la E / S síncrona basada en subprocesos; si su programa está vinculado a la CPU (por ejemplo, cálculos largos), la multitarea cooperativa será mucho menos útil.La programación cooperativa es la culpable. Sin ceder, la otra goroutine (digamos "mundo") puede tener legalmente cero oportunidades de ejecutarse antes / cuando finalice main, lo que por especificaciones termina todas las gorutines, es decir. Todo el proceso.
fuente
runtime.Gosched()
cede. Qué significa eso? ¿Devuelve el control a la función principal?