Si consiglia di leggere @Not_a_Golfer rispondere prima e il collegamento ha fornito per capire come goroutines sono in programma. La mia risposta è più simile a un tuffo nella rete IO in particolare. Immagino tu capisca come Go raggiunge il multitasking cooperativo.
Go può utilizzare solo le chiamate di blocco perché tutto viene eseguito in goroutine e non sono veri thread del sistema operativo. Sono fili verdi.Quindi è possibile che molti di loro bloccino tutte le chiamate di I/O e non mangeranno tutta la memoria e CPU come i thread del sistema operativo.
File IO è solo syscalls. Not_a_Golfer l'ha già coperto. Go userà il thread del sistema operativo reale per attendere su un syscall e sbloccherà la goroutine quando ritorna. Here è possibile vedere l'implementazione del file read
per Unix.
L'I/O di rete è diverso. Il runtime utilizza "poller di rete" per determinare quale goroutine deve sbloccare dalla chiamata IO. A seconda del sistema operativo di destinazione utilizzeranno le API asincrone disponibili per attendere gli eventi IO di rete. Le chiamate sembrano bloccate, ma all'interno di tutto viene eseguito in modo asincrono.
Ad esempio, quando si chiama read
sul socket TCP, goroutine proverà a leggere utilizzando syscall. Se non è ancora arrivato, bloccherà e attenderà che venga ripreso. Bloccando qui intendo un parcheggio che mette la goroutine in coda dove attende di riprendere. Questo è il modo in cui la goroutine "bloccata" rende l'esecuzione ad altre goroutine quando si utilizza l'IO di rete.
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
Quando il dato arriva poller rete tornerà goroutines che deve essere ripreso. Puoi vedere la funzione herefindrunnable
che cerca le goroutine che possono essere eseguite. Chiama la funzione netpoll
che restituirà le goroutine che possono essere riprese. È possibile trovare l'implementazione kqueue
di netpoll
here.
Per quanto riguarda async/wait in C#. l'IO della rete asincrona utilizzerà anche le API asincrone (porte di completamento dell'IO su Windows). Quando qualcosa arriva, il sistema operativo eseguirà la richiamata su uno dei thread della porta di completamento del threadpool che metterà la continuazione sull'attuale SynchronizationContext
. In un certo senso, ci sono alcune somiglianze (parcheggiare/non parcheggiare sembra come chiamare continuazioni ma su un livello molto più basso) ma questi modelli sono molto diversi, per non parlare delle implementazioni. Le goroutine di default non sono legate a un thread specifico del sistema operativo, possono essere ripristinate su ognuna di esse, non importa. Non ci sono thread UI da gestire. Async/await sono specificatamente realizzati per riprendere il lavoro sullo stesso thread del sistema operativo utilizzando SynchronizationContext
. E poiché non ci sono thread verdi o un programma di pianificazione separato async/await deve suddividere la funzione in più callback che vengono eseguiti su SynchronizationContext
, che è fondamentalmente un ciclo infinito che controlla una coda di callback che deve essere eseguita. Puoi persino implementarlo da solo, è davvero facile.
Vorrei aggiungere che lo schedulatore di runtime Go attualmente (Go 1.6 e sotto) multiplex (epoll su Linux, IOCP su Windows ecc.) Solo syscalls di I/O di rete. Tutti i sysc di I/O che colpiscono disco, seriale ecc. Occupano ciascuno un thread OS singolo. Se questo è buono o cattivo è discutibile nella comunità degli sviluppatori Go. Il consenso attuale sembra essere che sarebbe bello avere l'I/O asincrono generale disponibile all'utente, ma dal punto di vista pratico non è proprio * quello * utile ... – kostix
... come in - se hai 1000 goroutine scrivere sullo stesso disco fisso allo stesso tempo in cui l'I/O async non è di grande aiuto; utilizzare uno scrittore dedicato e un canale bufferizzato. Nota a parte: esistono pacchetti di terze parti che espongono l'interfaccia async/poller del sistema operativo sottostante. – kostix