Poseemos la mejor solución que descubrimos en línea. Nosotros deseamos que te resulte útil y si puedes comentarnos alguna mejora hazlo con total libertad.
Solución:
Go tiene un programador que le permite escribir código síncrono, cambia de contexto por sí solo y usa IO asíncrono bajo el capó. Entonces, si está ejecutando varias goroutines, es posible que se ejecuten en un solo hilo del sistema, y cuando su código se bloquea desde la vista de la goroutine, en realidad no lo está. No es magia, pero sí, te oculta todas estas cosas.
El programador asignará subprocesos del sistema cuando sean necesarios y durante las operaciones que realmente bloquean (creo que el archivo IO está bloqueando, por ejemplo, o llamando al código C). Pero si está usando un servidor http simple, puede tener miles y miles de gorutinas usando en realidad un puñado de “hilos reales”.
Puede leer más sobre el funcionamiento interno de Go aquí:
https://morsmachine.dk/go-scheduler
Primero debe leer la respuesta de @Not_a_Golfer y el enlace que proporcionó para comprender cómo se programan las rutinas de gor. Mi respuesta es más como una inmersión más profunda en la red IO específicamente. Supongo que comprende cómo Go logra la multitarea cooperativa.
Go puede usar y usa solo llamadas de bloqueo porque todo se ejecuta en goroutines y no son subprocesos reales del sistema operativo. Son hilos verdes. Por lo tanto, puede tener muchos de ellos bloqueando todas las llamadas IO y no consumirán toda su memoria y CPU como lo harían los subprocesos del sistema operativo.
File IO es solo llamadas al sistema. Not_a_Golfer ya cubrió eso. Go usará un hilo de SO real para esperar una llamada al sistema y desbloqueará la goroutine cuando regrese. Aquí puedes ver el archivo read
implementación para Unix.
Network IO es diferente. El tiempo de ejecución utiliza un “sondeo de red” para determinar qué rutina debe desbloquearse de la llamada IO. Dependiendo del sistema operativo de destino, utilizará las API asíncronas disponibles para esperar los eventos de E / S de la red. Parece que las llamadas se bloquean, pero por dentro todo se realiza de forma asincrónica.
Por ejemplo, cuando llamas read
en el socket TCP, goroutine primero intentará leer usando syscall. Si aún no llega nada, se bloqueará y esperará a que se reanude. Al bloquear aquí me refiero a un aparcamiento que pone a la goroutine en una cola donde espera reanudarse. Así es como la goroutine “bloqueada” produce la ejecución de otras goroutines cuando utiliza E / S de red.
func (fd *netFD) Read(p []byte) (n int, err error)
if err := fd.readLock(); err != nil
return 0, err
defer fd.readUnlock()
if err := fd.pd.PrepareRead(); err != nil
return 0, err
for
n, err = syscall.Read(fd.sysfd, p)
if err != nil
n = 0
if err == syscall.EAGAIN
if err = fd.pd.WaitRead(); err == nil
continue
err = fd.eofError(n, err)
break
if _, ok := err.(syscall.Errno); ok
err = os.NewSyscallError("read", err)
return
https://golang.org/src/net/fd_unix.go?s=#L237
Cuando lleguen los datos, el sondeo de la red devolverá las rutinas que deben reanudarse. Puedes ver aqui findrunnable
función que busca gorutinas que se pueden ejecutar. Llama netpoll
función que devolverá goroutines que se pueden reanudar. Puedes encontrar kqueue
implementación de netpoll
aquí.
En cuanto a async / wait en C #. La E / S de red asíncrona también utilizará API asincrónicas (puertos de finalización de E / S en Windows). Cuando llega algo, el sistema operativo ejecutará la devolución de llamada en uno de los subprocesos del puerto de finalización del grupo de subprocesos que pondrá la continuación en el actual SynchronizationContext
. En cierto sentido, existen algunas similitudes (estacionar / desaparcar parece llamadas continuas pero en un nivel mucho más bajo) pero estos modelos son muy diferentes, sin mencionar las implementaciones. Las gorutinas de forma predeterminada no están vinculadas a un subproceso específico del sistema operativo, se pueden reanudar en cualquiera de ellas, no importa. No hay subprocesos de interfaz de usuario con los que lidiar. Async / await están hechos específicamente con el propósito de reanudar el trabajo en el mismo hilo del sistema operativo usando SynchronizationContext
. Y debido a que no hay subprocesos verdes o un programador separado async / await, debe dividir su función en múltiples devoluciones de llamada que se ejecutan en SynchronizationContext
que es básicamente un bucle infinito que comprueba una cola de devoluciones de llamada que deberían ejecutarse. Incluso puedes implementarlo tú mismo, es realmente fácil.