Numero massimo di goroutine

Quante goroutine posso usare indolore? Ad esempio, dice wikipedia, in Erlang è ansible creare 20 milioni di processi senza degradare le prestazioni.

Aggiornamento: ho appena esaminato un po ‘le prestazioni delle goroutine e ho ottenuto risultati del genere:

  • Sembra che la durata della goroutine sia più che calcolatrice di sqrt () 1000 volte (~ 45μs per me), l’unica limitazione è la memoria
  • La goroutine costa 4 – 4,5 KB

Se una goroutine è bloccata, non vi sono costi aggiuntivi diversi da:

  • utilizzo della memoria
  • raccolta dei rifiuti più lenta

I costi (in termini di memoria e tempo medio per avviare effettivamente l’esecuzione di una goroutine) sono:

Go 1.6.2 (April 2016) 32-bit x86 CPU (A10-7850K 4GHz) | Number of goroutines: 100000 | Per goroutine: | Memory: 4536.84 bytes | Time: 1.634248 µs 64-bit x86 CPU (A10-7850K 4GHz) | Number of goroutines: 100000 | Per goroutine: | Memory: 4707.92 bytes | Time: 1.842097 µs Go release.r60.3 (December 2011) 32-bit x86 CPU (1.6 GHz) | Number of goroutines: 100000 | Per goroutine: | Memory: 4243.45 bytes | Time: 5.815950 µs 

Su una macchina con 4 GB di memoria installata, questo limita il numero massimo di goroutine a poco meno di 1 milione.


Codice sorgente (non è necessario leggerlo se hai già capito i numeri stampati sopra):

 package main import ( "flag" "fmt" "os" "runtime" "time" ) var n = flag.Int("n", 1e5, "Number of goroutines to create") var ch = make(chan byte) var counter = 0 func f() { counter++ <-ch // Block this goroutine } func main() { flag.Parse() if *n <= 0 { fmt.Fprintf(os.Stderr, "invalid number of goroutines") os.Exit(1) } // Limit the number of spare OS threads to just 1 runtime.GOMAXPROCS(1) // Make a copy of MemStats var m0 runtime.MemStats runtime.ReadMemStats(&m0) t0 := time.Now().UnixNano() for i := 0; i < *n; i++ { go f() } runtime.Gosched() t1 := time.Now().UnixNano() runtime.GC() // Make a copy of MemStats var m1 runtime.MemStats runtime.ReadMemStats(&m1) if counter != *n { fmt.Fprintf(os.Stderr, "failed to begin execution of all goroutines") os.Exit(1) } fmt.Printf("Number of goroutines: %d\n", *n) fmt.Printf("Per goroutine:\n") fmt.Printf(" Memory: %.2f bytes\n", float64(m1.Sys-m0.Sys)/float64(*n)) fmt.Printf(" Time: %f µs\n", float64(t1-t0)/float64(*n)/1e3) } 

Domande frequenti su centinaia di migliaia, per Go: perché le goroutine invece dei thread? :

È pratico creare centinaia di migliaia di goroutine nello stesso spazio degli indirizzi.

Il test test / chan / goroutines.go crea 10.000 e potrebbe facilmente fare di più, ma è progettato per funzionare rapidamente; puoi cambiare il numero sul tuo sistema per sperimentare. È ansible eseguire facilmente milioni, data memoria sufficiente, come su un server.

Per comprendere il numero massimo di goroutine, si noti che il costo per-goroutine è principalmente lo stack. Per FAQ di nuovo:

… le goroutine, possono essere molto economiche: hanno poco sopra la memoria per la pila, che è solo pochi kilobyte.

Un calcolo di back-of-the-envelop consiste nell’assumere che ogni goroutine abbia una pagina di 4 KiB allocata per lo stack (4 KiB è una dimensione abbastanza uniforms), più un piccolo overhead per un blocco di controllo (come un blocco di controllo del thread ) per il runtime; questo concorda con ciò che hai osservato (nel 2011, pre-Go 1.0). Quindi le routine di 100 Ki richiederebbero circa 400 MiB di memoria e le routine di 1 Mi richiederebbero circa 4 GB di memoria, che è ancora gestibile su desktop, un po ‘troppo per un telefono e molto gestibile su un server. In pratica, la pila iniziale ha dimensioni variabili da mezza pagina (2 KiB) a due pagine (8 KiB), quindi è approssimativamente corretta.

La dimensione dello stack iniziale è cambiata nel tempo; è iniziato a 4 KiB (una pagina), quindi in 1,2 è stato aumentato a 8 KiB (2 pagine), quindi in 1,4 è stato diminuito a 2 KiB (mezza pagina). Queste modifiche erano dovute a stack segmentati che causavano problemi di prestazioni quando si passava rapidamente avanti e indietro tra segmenti (“hot stack split”), quindi aumentati fino a mitigarli (1.2), quindi diminuiti quando gli stack segmentati venivano sostituiti con stack contigui (1.4):

Note di rilascio di Go 1.2: Dimensione dello stack :

In Go 1.2, la dimensione minima dello stack quando viene creata una goroutine è stata sollevata da 4KB a 8 KB

Vai 1.4 Note di rilascio: modifiche al runtime :

la dimensione iniziale predefinita per lo stack di una goroutine in 1.4 è stata ridotta da 8192 byte a 2048 byte.

La memoria per-goroutine è in gran parte stack, e inizia a scarseggiare e cresce quindi puoi avere molte goroutine a basso costo. Potresti usare uno stack iniziale più piccolo, ma poi dovrebbe crescere prima (guadagnare spazio al costo del tempo) e i benefici diminuiscono a causa del blocco del controllo che non si riduce. È ansible eliminare lo stack, almeno quando viene eseguito lo swapping (ad esempio, eseguire tutte le allocazioni nell’heap o salvare lo stack per l’heap sullo switch di contesto), anche se questo danneggia le prestazioni e aggiunge complessità. Questo è ansible (come in Erlang), e significa che avresti solo bisogno del blocco di controllo e del contesto salvato, consentendo un altro fattore di 5 × -10 × in numero di goroutine, limitato ora dalla dimensione del blocco di controllo e dalla dimensione dell’heap della goroutine – variabili locali Tuttavia, questo non è terribilmente utile, a meno che tu non abbia bisogno di milioni di piccole goroutine addormentate.

Poiché l’uso principale di avere molte goroutine è per attività legate all’IO (in concreto per elaborare i blocchi di sicurezza, in particolare l’I / O di rete o di file system), è molto più probabile imbattersi nei limiti del sistema operativo su altre risorse, vale a dire socket di rete o handle di file : golang-nuts> Il numero massimo di goroutine e descrittori di file? . Il modo usuale per affrontare questo è con un pool della risorsa scarsa, o più semplicemente limitando semplicemente il numero tramite un semaforo ; vedere Conservare i descrittori di file in Go e Limitare la concorrenza in Go .

Dipende interamente dal sistema su cui stai lavorando. Ma le goroutine sono molto leggere. Un processo medio non dovrebbe presentare problemi con 100.000 routine simultanee. Ovviamente, ciò vale per la tua piattaforma di destinazione, è qualcosa a cui non possiamo rispondere senza sapere quale sia quella piattaforma.

Per parafrasare, ci sono bugie, maledette bugie e benchmark. Come ha confessato l’autore del benchmark di Erlang,

Inutile dire che non c’era abbastanza memoria nella macchina per fare effettivamente qualcosa di utile. stress-test erlang

Qual è il tuo hardware, qual è il tuo sistema operativo, dov’è il tuo codice sorgente di riferimento? Qual è il parametro che tenta di misurare e dimostrare / confutare?

Ecco un grande articolo di Dave Cheney su questo argomento: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

Se il numero di goroutine diventa mai un problema, puoi facilmente limitarlo per il tuo programma:
Vedi mr51m0n / gorc e questo esempio .

Imposta le soglie sul numero di goroutine in esecuzione

Può aumentare o diminuire un contatore quando si avvia o si arresta una goroutine.
Può attendere il funzionamento di un numero minimo o massimo di goroutine, consentendo così di impostare le soglie per il numero di goroutines governate da gorc esecuzione contemporaneamente.