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

Comment d’autres goroutines peuvent-elles continuer à s’exécuter en invoquant un appel système? (en utilisant GOMAXPROCS = 1)
Autant que je sache, en appelant un appel syscall, le thread abandonne le contrôle jusqu’à ce que syscall retourne. Comment peut-on atteindre cette concurrence sans créer un thread système par goroutine bloquant sur syscall?

De la documentation :

Goroutines

Ils sont appelés goroutines car les termes existants (threads, coroutines, processus, etc.) transmettent des connotations inexactes. Une goroutine a un modèle simple: il s’agit d’une fonction s’exécutant simultanément avec d’autres goroutines dans le même espace d’adressage. Il est léger et ne coûte guère plus que l’allocation d’espace de stack. Et les stacks commencent peu, donc elles sont bon marché et se développent en allouant (et libérant) le stockage de tas selon les besoins.

Les Goroutines sont multiplexées sur plusieurs threads du système d’exploitation. Par conséquent, si quelqu’un doit bloquer, par exemple en attendant les E / S, les autres continuent à s’exécuter. Leur conception cache beaucoup des complexités de la création et de la gestion des threads.

Si une goroutine bloque, le runtime lance un nouveau thread OS pour gérer les autres goroutines jusqu’à ce que le blocage cesse de bloquer.

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

Ok, alors voici ce que j’ai appris: lorsque vous faites des appels système bruts, Go crée en effet un thread par goroutine bloquante. 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, 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) } 

Donc, c'est un mélange entre un IOLoop et un thread par appel système bloquant.

Ça ne peut pas. Il n’y a qu’une seule Goroutine qui peut être exécutée à la fois quand GOMAXPROCS = 1, que ce soit un goroutine qui effectue 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 timer ne sont pas bloqués lors d’un appel système lorsqu’ils sont exécutés à partir de Go. Ils sont multiplexés par l’environnement d’exécution Go sur des fonctions epoll, kqueue ou similaires, fournies par l’OS pour le multiplexage des E / S.

Pour les autres types d’appels système 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 (même si c’était l’état dans Go 1.1, je ne suis pas sûr que la situation change)

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 encore s’exécuter sur un seul processeur.

Comme l’explique la documentation que vous avez citée, une goroutine n’est pas un thread: c’est simplement une fonction exécutée dans un thread dédié à un morceau d’espace de stack. 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 bloquant pour une raison ou une autre (syscall, E / S, synchronisation) peut être laissée dans son thread alors que d’autres routines peuvent être exécutées par un autre.