web-dev-qa-db-fra.com

Comment fonctionnent les goroutines? (ou: relation goroutines et threads OS)

Comment d'autres goroutines peuvent-elles continuer à s'exécuter tout en invoquant un appel système? (lors de l'utilisation de GOMAXPROCS = 1)
Pour autant que je sache, lors de l'appel d'un syscall, le thread abandonne le contrôle jusqu'à ce que le syscall revienne. Comment Go peut-il atteindre cette concurrence sans créer de thread système par goroutine blocking-on-syscall?

De la documentation :

Goroutines

Ils sont appelés goroutines parce que les termes existants - fils, coroutines, processus, etc. - véhiculent des connotations inexactes. Un goroutine a un modèle simple: c'est une fonction qui s'exécute simultanément avec d'autres goroutines dans le même espace d'adressage. Il est léger et coûte un peu plus cher que l'allocation d'espace de pile. Et les piles commencent petit, elles sont donc bon marché et augmentent en allouant (et en libérant) le stockage en tas selon les besoins.

Les goroutines sont multiplexées sur plusieurs threads du système d'exploitation, donc si l'un doit se bloquer, par exemple en attendant les E/S, d'autres continuent de fonctionner. Leur conception cache de nombreuses complexités de la création et de la gestion des threads.

54
omribahumi

Si un goroutine bloque, le runtime démarrera un nouveau thread OS pour gérer les autres goroutines jusqu'à ce que le blocage arrête de bloquer.

Référence: https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ

35
OneOfOne

Ok, voici donc ce que j'ai appris: lorsque vous effectuez des appels système bruts, Go crée en effet un fil par goroutine bloquant. Par exemple, considérez le code suivant:

package main

import (
        "fmt"
        "syscall"
)

func block(c chan bool) {
        fmt.Println("block() enter")
        buf := make([]byte, 1024)
        _, _ = syscall.Read(0, buf) // block on doing an unbuffered read on STDIN
        fmt.Println("block() exit")
        c <- true // main() we're done
}

func main() {
        c := make(chan bool)
        for i := 0; i < 1000; i++ {
                go block(c)
        }
        for i := 0; i < 1000; i++ {
                _ = <-c
        }
}

Lors de son exécution, Ubuntu 12.04 a signalé 1004 threads pour ce processus.

En revanche, lors de l'utilisation du serveur HTTP de Go et de l'ouverture de 1000 sockets sur celui-ci, seuls 4 threads de système d'exploitation ont été créés:

package main

import (
        "fmt"
        "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
        http.HandleFunc("/", handler)
        http.ListenAndServe(":8080", nil)
}

Il s'agit donc d'un mélange entre un IOLoop et un thread par appel système bloquant.

26
omribahumi

Ça ne peut pas. Il n'y a qu'un seul goroutine qui peut être exécuté à un moment où GOMAXPROCS = 1, que ce goroutine fasse un appel système ou autre chose.

Cependant, la plupart des appels système bloquants, tels que les E/S de socket, en attente d'une minuterie ne sont pas bloqués lors d'un appel système lorsqu'ils sont effectués à partir de Go. Ils sont multiplexés par le runtime Go sur epoll, kqueue ou des installations similaires que le système d'exploitation fournit pour les E/S de multiplexage.

Pour d'autres types d'appels système bloquants qui ne peuvent pas être multiplexés avec quelque chose comme epoll, Go génère un nouveau thread OS, quel que soit le paramètre GOMAXPROCS (bien que ce soit l'état dans Go 1.1, je ne suis pas sûr que la situation soit modifiée)

13
nos

Vous devez différencier le numéro de processeur et le numéro de thread: vous pouvez avoir plus de threads que de processeurs physiques, de sorte qu'un processus multi-thread peut toujours s'exécuter sur un seul processeur.

Comme l'explique la documentation que vous avez citée, un goroutine n'est pas un thread: c'est simplement une fonction exécutée dans un thread dédié à un morceau d'espace de pile. Si votre processus a plus d'un thread, cette fonction peut être exécutée par l'un ou l'autre thread. Ainsi, une goroutine qui bloque pour une raison ou une autre (appel système, E/S, synchronisation) peut être laissée dans son thread tandis que d'autres routines peuvent être exécutées par une autre.

5
Elwinar