web-dev-qa-db-fra.com

Blocage et non blocage de Golang

Je suis quelque peu confus quant à la façon dont Go gère les E/S non bloquantes. Les API me semblent principalement synchrones, et lorsque vous regardez des présentations sur Go, il n'est pas rare d'entendre des commentaires comme "et les blocs d'appels"

Go utilise-t-il le blocage IO lors de la lecture à partir de fichiers ou d'un réseau? Ou existe-t-il une sorte de magie qui réécrit le code lorsqu'il est utilisé à partir d'une routine Go?

Venant d'un arrière-plan C #, cela semble très peu intuitif, en C #, nous avons le mot clé await lors de la consommation des API asynchrones. Ce qui indique clairement que l'API peut générer le thread actuel et continuer plus tard dans une continuation.

Donc TLDR; Est-ce que Go bloquera le thread en cours lors de l'exécution de IO dans une routine Go, ou sera-t-il transformé en C # comme une asynchrone attend la machine d'état en utilisant des continuations?

31
Roger Johansson

Go a un ordonnanceur qui vous permet d'écrire du code synchrone, et fait le changement de contexte de son propre chef et utilise async IO sous le capot. Donc, si vous exécutez plusieurs goroutines, ils peuvent s'exécuter sur un seul thread système, et quand votre code bloque du point de vue du goroutine, ce n'est pas vraiment bloquant. Ce n'est pas magique, mais oui, il masque tout ça.

Le planificateur allouera les threads système quand ils sont nécessaires et pendant les opérations qui bloquent vraiment (je pense que le fichier IO bloque par exemple, ou appelle du code C). Mais si vous faites un simple serveur http, vous pouvez avoir des milliers et des milliers de goroutine en utilisant en fait une poignée de "vrais threads".

Vous pouvez en savoir plus sur le fonctionnement interne de Go ici:

https://morsmachine.dk/go-scheduler

32
Not_a_Golfer

Vous devriez lire d'abord la réponse @Not_a_Golfer et le lien qu'il a fourni pour comprendre comment les goroutines sont planifiées. Ma réponse ressemble plus à une plongée dans le réseau IO spécifiquement. Je suppose que vous comprenez comment Go réalise le multitâche coopératif.

Go peut utiliser et ne bloque que les appels, car tout fonctionne dans des goroutines et ce ne sont pas de vrais threads du système d'exploitation. Ce sont des fils verts. Ainsi, vous pouvez avoir beaucoup d'entre eux bloquant tous les appels IO et ils ne mangeront pas toute votre mémoire et votre processeur comme le feraient les threads du système d'exploitation.

Fichier IO n'est que des appels système. Not_a_Golfer l'a déjà couvert. Go utilisera un véritable thread du système d'exploitation pour attendre un appel système et débloquera le goroutine à son retour. Ici vous pouvez voir l'implémentation du fichier read pour Unix.

Network IO is different. Le runtime utilise "network poller" pour déterminer quel goroutine doit débloquer de IO call. En fonction du système d'exploitation cible, il utilisera le système asynchrone disponible) API pour attendre les événements réseau IO. Les appels ressemblent à un blocage mais à l'intérieur tout se fait de manière asynchrone.

Par exemple, lorsque vous appelez read on TCP socket goroutine essaiera d'abord de lire en utilisant syscall. Si rien n'est encore arrivé, il se bloquera et attendra sa reprise. Par bloquer ici, je veux dire un parking qui place le goroutine dans une file d'attente où il attend la reprise.

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

Lorsque les données arrivent, le scrutateur réseau renverra des goroutines qui devraient être reprises. Vous pouvez voir icifindrunnable fonction qui recherche les goroutines qui peuvent être exécutées. Il appelle la fonction netpoll qui retournera des goroutines qui peuvent être reprises. Vous pouvez trouver kqueue implémentation de netpollici .

Quant à async/wait en C #. réseau asynchrone IO utilisera également des API asynchrones (ports d'achèvement d'E/S sous Windows). Lorsque quelque chose arrive, le système d'exploitation exécutera un rappel sur l'un des threads du port d'achèvement du threadpool qui mettra la continuation sur le SynchronizationContext. Dans un sens, il y a quelques similitudes (parking/unparking ressemble à appeler des continuations mais à un niveau beaucoup plus bas) mais ces modèles sont très différents, sans parler des implémentations. Les goroutines par défaut ne sont pas liées à un spécifique Thread OS, ils peuvent être repris sur n'importe lequel d'entre eux, cela n'a pas d'importance. Il n'y a pas de threads d'interface utilisateur à traiter. Async/attendent sont spécifiquement conçus dans le but de reprendre le travail sur le même thread OS en utilisant SynchronizationContext. Et parce qu'il n'y a pas de threads verts ou que le planificateur asynchrone/attend séparé doit diviser votre fonction en plusieurs rappels qui s'exécutent sur SynchronizationContext qui est essentiellement une boucle infinie qui vérifie une file d'attente de rappels qui doit être exécuté. Vous pouvez même l'implémenter vous-même, c'est re allié facile.

19
creker

Go bloquera le goroutine actuel lors de l'exécution de IO ou syscalls, mais lorsque cela se produit, un autre goroutine est autorisé à s'exécuter à la place du goroutine bloqué. Les anciennes versions de Go n'autorisaient qu'un seul goroutine en cours d'exécution à la fois , mais depuis la version 1.5, ce nombre est devenu le nombre de cœurs de processeur disponibles. ( runtime.GOMAXPROCS )

Vous n'avez pas à vous soucier du blocage dans Go. Par exemple, le serveur http de la bibliothèque standard exécute vos fonctions de gestionnaire dans un goroutine. Si vous essayez de lire un fichier tout en servant une demande http, cela se bloquera, mais si une autre demande arrive alors que la première est bloquée, une autre goroutine sera autorisée à s'exécuter et à servir cette demande. Ensuite, lorsque le deuxième goroutine est terminé et que le premier n'est plus bloqué, il reprendra (si GOMAXPROCS> 1, le goroutine bloqué pourrait être repris encore plus tôt s'il y a un fil libre).

Pour plus d'informations, consultez ces:

0
user1431317