Saltar al contenido

Bloqueo y no bloqueo de Golang

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *