2016-03-20 20 views
15

Sono un po 'confuso su come GO gestisce l'I/O non bloccante. Le API per lo più sembrano sincrone per me e quando si guardano le presentazioni su Go, non è raro ascoltare commenti come "e i blocchi di chiamata"Blocco di Golang e non blocco

È possibile utilizzare IO di blocco durante la lettura da file o rete? O c'è qualche tipo di magia che riscrive il codice quando viene usato da una routine di go?

Proveniente da uno sfondo C#, questo sembra molto non intuitivo, in C# abbiamo la parola chiave await quando si utilizzano API asincrone. Che comunica chiaramente che l'API può fornire il thread corrente e continuare in seguito in una continuazione.

Quindi TLDR; Will Go bloccherà il thread corrente quando si esegue IO all'interno di una routine Go, o verrà trasformato in un C# come asincrono attendendo la macchina a stati usando le continuazioni?

risposta

20

Go dispone di un programma di pianificazione che consente di scrivere codice sincrono e fa il cambio di contesto da solo e utilizza IO asincrono sotto il cofano. Quindi, se stai eseguendo diverse goroutine, potrebbero essere eseguite su un singolo thread di sistema, e quando il codice sta bloccando dalla vista della goroutine, non si tratta di un blocco reale. Non è magico, ma sì, maschera tutte queste cose da te.

Lo scheduler alloca i thread di sistema quando sono necessari e durante operazioni che sono realmente bloccanti (penso che il file IO stia bloccando, per esempio, o chiamando il codice C). Ma se stai facendo un semplice server http, puoi avere migliaia e migliaia di goroutine usando in realtà una manciata di "thread reali".

Si può leggere di più sul funzionamento interno di andare qui:

https://morsmachine.dk/go-scheduler

+2

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

+2

... 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

-1

Go blocca il goroutine corrente quando si fa IO o chiamate di sistema, ma quando questo accade, un altro goroutine è consentita l'esecuzione invece di la goroutine bloccata. Le versioni precedenti di Go consentivano solo una goroutine alla volta, ma a partire da 1.5 quel numero è cambiato in numero di core CPU disponibili. (runtime.GOMAXPROCS)

Non devi preoccuparti di bloccare in Go. Ad esempio, il server http della libreria standard esegue le funzioni del gestore in una goroutine. Se provi a leggere un file mentre stai servendo una richiesta http, questo verrà bloccato, ma se arriva un'altra richiesta mentre il primo è bloccato, un'altra goroutine potrà eseguire e servire quella richiesta. Quindi, quando viene eseguita la seconda goroutine e il primo non viene più bloccato, verrà ripreso (se GOMAXPROCS> 1, la goroutine bloccata potrebbe essere ripresa anche prima se c'è un thread libero).

Per maggiori informazioni, dai un'occhiata a queste:

11

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 netpollhere.

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.

+2

Penso che ci sia un problema semantico con la parola "block" qui, se la routine Go produce e può essere risvegliata in un secondo momento, allora ci deve essere qualcosa all'interno di quel codice che fa funzionare, ad es. continuazione passando stile o qualcosa del genere. no? quindi agisce come se stesse bloccando, ma dietro le quinte produce un'esecuzione e più tardi si risveglia e continua? Suppongo che se ho un ciclo infinito per un ciclo all'interno di una routine Go, quella routine Go non può mai esistere e il thread attualmente in esecuzione la routine Go viene bloccato per sempre, giusto? Se questo non è il caso, sono completamente confuso qui. –

+2

Dovresti prima leggere la risposta @Not_a_Golfer e il link che ha fornito per capire come sono programmate le goroutine. La mia risposta è più simile a un tuffo nella rete IO in particolare. Sì, il significato di "blocco" dipende dal contesto. Dal punto di vista dei programmatori, blocca. Il tuo codice blocca e non continua fino a quando la chiamata non ritorna. Dal punto di vista del runtime produce l'esecuzione. È per questo che l'ho chiamato parcheggio: è un termine reale usato in Go. Il multitasking cooperativo e il ciclo infinito bloccano per sempre la goroutine e il thread del sistema operativo perché non produrranno mai esecuzione. – creker

+0

@RogerAlsing yes, se una goroutine non fa mai nulla che "blocchi" e non chiama mai "runtime.Gosched" (che è un rendimento esplicito dello scheduler) occuperà la P indefinitamente, impedendo ad altre goroutine di avviarsi su di essa. – hobbs

Problemi correlati