Cosa può fare Go chan che una lista non può?

2

Voglio sapere in quale situazione Go chan rende il codice molto più semplice rispetto all'utilizzo di elenchi o code o array solitamente disponibili in tutte le lingue.

Come affermato da Rob Pike in uno dei suoi discorsi su Go lexer, i canali Go aiutano a organizzare il flusso di dati tra strutture che non sono omomorfiche.

Mi interessa un semplice esempio di codice Go con chan che diventa MOLTO più complicato in un'altra lingua (ad esempio C #) in cui chan non è disponibile.

Non mi interessano gli esempi che usano chan solo per aumentare le prestazioni evitando l'attesa dei dati tra la generazione dell'elenco e il consumo dell'elenco (che può essere risolto mediante chunking) o come un modo per organizzare la coda thread-safe o la comunicazione thread-safe (che può essere facilmente risolto bloccando le primitive).

Mi interessa un esempio che renda il codice più semplice strutturalmente ignorando la dimensione dei dati.

Se tale campione non esiste, campionare la dimensione dei dati.

Immagino che il campione desiderato contenga una comunicazione bidirezionale tra generatore e consumatore.

Inoltre, se qualcuno potesse aggiungere il tag [canale] all'elenco dei tag disponibili, sarebbe fantastico.

    
posta alpav 13.03.2013 - 06:41
fonte

3 risposte

10

Generalmente una coda o un array generici non sono, da soli, thread-safe, proprio come molti altri tipi di dati. La sicurezza del filo si ottiene in genere in due modi:

  • Uso dei blocchi mutex - ogni thread che desidera modificare un valore deve attendere.
  • Delega - solo il thread proprietario può modificare il valore.

I blocchi di Mutex sono abbastanza semplici: nessuno possiede il valore, ma solo un thread può modificarlo alla volta. Quando un thread modifica un valore, altri thread devono attendere. Il problema con questo approccio è contention . Più di un thread che desidera modificare un valore allo stesso tempo e il numero di thread in attesa di stack.

La gestione della delega dipende in realtà dalla lingua in questione. In Objective C è necessario che GCD invii richieste asincrone (o anche sincrone) sul thread proprietario. Hai la possibilità di (in modo asincrono) di modificare il valore senza tenere premuto il thread di richiesta. GCD programma internamente queste attività in una coda e le esegue quando il thread di destinazione non è occupato.

I canali Go sono in qualche modo una forma di delega. Puoi usarlo come coda non bloccante usando i canali bufferizzati. Ma la più grande differenza tra i canali di Go e altri approcci può probabilmente essere riassunta con la seguente frase:

Don't communicate by sharing, share by communicating.

Un canale Go è molto più di una semplice coda, è una pipeline di comunicazione. Il modo migliore per illustrarlo è mostrare un estratto di uno dei miei progetti:

for {
    select {
    case client := <-server.accept:
        server.addClient(client)
        go server.serveClient(client)
    case client := <-server.remove:
        server.removeClient(client)
    case envelope := <-server.dropbox:
        switch envelope.cmd {
        case "pub":
            server.publish(envelope)
        case "sub":
            server.subscribe(envelope)
        case "unsub":
            server.unsubscribe(envelope)
        }
    case err := <-server.errors:
        return err
    }
}

Il codice sopra riportato è in esecuzione sulla routine Go principale (i canali sono bufferizzati).

<-server.accept - Una seconda routine Go (non mostrata qui) gestisce le nuove connessioni e le passa al canale di accettazione. La routine Go principale accetta il nuovo client e li aggiunge a una mappa di client connessi. Un'altra routine Go viene generata per gestire le richieste dal nuovo client.

<-server.remove - Quando un client disconnette la routine Go che gestisce la sua connessione, la passa nuovamente alla routine Go principale da rimuovere.

<-server.dropbox - Quando un client ha qualcosa che desidera inviare, lo mette nella "casella di riepilogo" per essere consegnato ad altri client, che sono registrati nella mappa.

Ogni client ha un canale inbox a cui il thread principale può consegnare "mail". La routine Go che gestisce la connessione client legge la posta in arrivo.

Come puoi vedere dall'esempio, il codice diventa in qualche modo riconoscibile: un ufficio postale con una casella di consegna per consegnare la posta e una casella di posta in arrivo per ogni cliente che la riceve. Questo è solo un esempio di come i canali possono essere usati.

I canali dovrebbero sempre essere usati in ogni situazione? No, ho visto situazioni in cui un blocco mutex aveva più senso di un canale. Le strutture di dati atomici sono un esempio in cui le serrature mutex hanno più senso.

Penso che l'obiettivo generale di un canale sia rendere il codice più facile da capire. Trasmette l'intenzione più precisamente, in alcune (ma molte) situazioni. Internamente un canale utilizza probabilmente dei blocchi mutex. Penso che un punto importante da portare a casa sia - i canali sono utili; solo non farti trasportare.

    
risposta data 13.03.2013 - 08:16
fonte
1

I want to know in which situation Go chan makes code much simpler than using list or queue or array that is usually available in all languages.

Diciamo che hai una routine che genera numeri di Fibonacci (come sostituto di qualcosa di più complesso). La routine principale ha bisogno di "ottenere il prossimo numero" quando ne ha bisogno. La routine principale non deve essere accoppiata alla routine di Fibonacci, quindi non deve memorizzare lo stato di Fibonacci.

Quindi dove memorizzi lo stato di Fibonacci tra una chiamata e l'altra? In una lingua senza thread / coroutine, è necessario scrivere molto codice aggiuntivo per memorizzare questo stato e il codice di archiviazione dovrebbe essere modificato ogni volta che è necessario memorizzare uno stato leggermente diverso. Il codice "memorizza lo stato" è fragile e non ha nulla a che fare con il calcolo effettivo.

Quindi è molto più semplice girare quella routine nel proprio thread di esecuzione. (vero filo del linguaggio). Quindi possiamo chiedergli di generare il valore successivo quando necessario e nessuna delle due preoccupazioni riguarda lo stato. Yay.

È possibile utilizzare gli array per questa comunicazione, ma è necessario scrivere una serie di codici: Blocco dell'array (per evitare gare), gestione del caso "array full", gestione del caso wrap-around dell'array, gestione dell'array caso di underflow, ecc. Una coda è più simile, ma non ha la stessa semantica (non sempre il mutithread è sicuro, non sveglia sempre il ricevitore, ecc.)

Avere un canale rende 100 volte più ovvio ciò che stai facendo. Una volta che ti sarai abituato ai canali, li userai sempre. Non avere canali è come non avere una struttura dati Map / Dictonary / Hash. La gente è sopravvissuta per molti anni senza di loro, ma una volta che li hai, vedi tutti i tipi di posti che sono utili. Inizierai ad organizzare il tuo codice attorno al concetto CSP .

Sono finiti i giorni in cui è possibile acquistare un computer single-core (o anche un telefono single-core). Pertanto, la comunicazione tra thread / processi diventerà sempre più importante nel tempo. Se tu (o la tua lingua) puoi solo pensare in termini di elaborazione a singola CPU, sarai un dinosauro.

La concorrenza non è il parallelismo, è meglio.

    
risposta data 27.12.2013 - 18:51
fonte
0

I canali e gli elenchi utilizzati da una coda sono molto simili. In go, i canali sono progettati per funzionare con le goroutine. Le goroutine consentono al tuo programma di utilizzare tutto il nucleo del tuo computer. I canali consentono alle goroutine di condividere i dati in modo sicuro attraverso i core. I canali consentono anche alle goroutine di sincronizzarsi con i dati.

    
risposta data 03.11.2013 - 05:34
fonte

Leggi altre domande sui tag