2013-08-08 18 views
6

Sto cercando di capire cosa succede quando si accede in modo concorrente a un metodo di puntatori?Accesso simultaneo ai metodi dei puntatori

Ho una mappa di puntatori e genera alcune routine di go. Passo la mappa in ciascuna routine di go e ogni routine userà uno dei valori nella mappa. Nulla viene scritto sulla mappa solo da cui viene letto.

La mappa è piccola, solo 4 tasti, quindi è possibile che più di una routine di go utilizzerà lo stesso valore dalla mappa.

La domanda è, cosa succede quando due routine di go chiamano un metodo dello stesso puntatore? Otterrò risultati imprevedibili?

EDIT

Esempio: Sto prendendo la porzione mappa come non è questo il problema che sto cercando.

Ho foo che è un puntatore del tipo MyStruct e questa struttura ha un metodo DoSomething che accetta gli argomenti. Nella funzione main sto creando due go routines ed entrambi fanno chiamate a foo.DoSomething passando valori diversi. In questo esempio, la prima routine di go ha un calcolo molto più grande rispetto alla seconda (utilizzando semplicemente i tempi di sospensione qui per simulare i calcoli). Di nuovo, nulla nella struttura sta cambiando. Sto solo facendo una chiamata al metodo delle strutture. Devo preoccuparmi che la routine di seconda uscita effettui una chiamata a foo.DoSomething quando la prima routine di go sta ancora lavorando con il metodo?

package main 

import (
    "log" 
    "time" 
) 

type MyStruct struct { 
} 

func (self *MyStruct) DoSomething(value int) { 

    log.Printf("%d Start", value) 

    calculation_time := time.Duration(value) * time.Second 
    log.Printf("%d Calculating", value, calculation_time) 
    time.Sleep(calculation_time) 

    log.Printf("%d Done", value) 
} 

func main() { 

    var foo = new(MyStruct) 

    go foo.DoSomething(5) 

      // is this method call a problem when the first one is still working? 
    go foo.DoSomething(2) 

    time.Sleep(time.Duration(6 * time.Second)) 
} 

risposta

7

I metodi Go hanno ricevitori. Receiver può essere un tipo di puntatore.Procedimento con la firma, ad esempio:

func (r *R) foo(bar baz) // A method 

rappresenta l'same come

func foo(r *R, bar baz) // A plain old function 

In altre parole, il ricevitore, puntatore o no, è solo uno slot argomento. La tua domanda ora si riduce a:

Cosa succede quando due go routine richiamano la funzione sopra con lo stesso valore di r?

A: Dipende. Configurazioni dei problemi:

  • foo non è rientrante per nessuna ragione.
  • foo muta *r (la punta) senza coordinazione/sincronizzazione.
  • L'ultimo punto è solo un caso particolare di: Se foo muta qualsiasi condivisi stato w/o di coordinamento/sincronizzazione: nulla può accadere.

Se foo evita i pozzi di catrame sopra allora spetta sicuro per essere eseguito contemporaneamente da più goroutines anche con lo stesso valore di r.

2

Qualsiasi puntatore è considerato non thread-safe. Una routine di go dovrebbe essere trattata come un thread separato, anche se potrebbe non esserlo. Le routine Go sono multiplexate su thread os.

Se il valore è sempre di sola lettura (non cambierà mai), è possibile leggere da quante routine go si desidera. Non appena cambi il valore otterrai risultati incoerenti.

Per sincronizzare l'accesso ed evitare problemi (e panico potenziale) è necessario utilizzare uno sync.RWMutex. Quindi, invece di leggere/scrivere direttamente, si usa una funzione getter e setter. Il getter userebbe m.RLock() e m.RUnlock(). Il setter userebbe m.Lock() e m.Unlock().

Quando si utilizzano i mutex, provare a sbloccare il più rapidamente possibile. Mantenere il codice tra un blocco e sbloccare più breve possibile:

m.Lock() 
// Do what you need to do for the lock 
mymap[key] = value 
m.Unlock() 
// Do everything else here 

sync.RWMutex è diverso da sync.Mutex in quanto permette di avere il maggior numero di lettori simultanei come si desidera (RLock sta per read-lock). Non appena uno scrittore tenta di prendere una serratura impedisce agli altri lettori di ottenere una serratura e attende che i lettori uscenti rilascino i loro lucchetti.

In alternativa è possibile utilizzare i canali per passare valori tra le routine di go. I canali funzionano per molte situazioni e incoraggiati. Puoi leggere ulteriori informazioni sulla concorrenza in Effective Go. Tuttavia, i canali non sempre si adattano ad ogni situazione, quindi dipende dalla situazione.

+0

grazie per il vostro tempo nell'aiutarmi, ma questa non è esattamente la domanda che cercavo. Ho aggiunto una modifica con un esempio di codice di ciò che sto cercando di ottenere. Ci scusiamo per qualsiasi confusione. – Jeff

+0

@Jeff Così come dice la mia risposta, "Se il valore è sempre di sola lettura (non cambierà mai), puoi leggere da quante routine go vuoi.". Non deve essere specifico per la mappa: è un puntatore. Lo stesso vale. – Luke

1

Si ottiene una condizione di competizione ogni volta che qualcuno modifica una variabile mentre qualcun altro la sta leggendo. Nel tuo esempio, la variabile foo/self viene letta da molte goroutine contemporaneamente, ma poiché nessuno la modifica mentre è accessibile contemporaneamente, tutto va bene.

Un esempio più interessante sarebbe una struttura MyStruct con alcuni attributi aggiuntivi, ad es. attribute1. In tal caso, valgono anche le stesse regole per l'attributo. Puoi leggerlo contemporaneamente da diverse goroutine - ad esempio il tuo metodo DoSomething potrebbe stampare anche il valore di self.attribute1 - ma non ti è permesso modificarlo durante quella durata.

Se si desidera poter modificare una variabile mentre vi si accede, è necessario un tipo di primitiva di sincronizzazione. L'approccio Go idiomatico sarebbe quello di evitare accessi concorrenti alla stessa variabile del tutto, utilizzando solo variabili locali a cui si accede da una singola goroutine e che comunicano su canali, ogni volta che sono necessari alcuni dati da un'altra goroutine.

Gli approcci alternativi disponibili anche in Go sono i primitivi del pacchetto sync, come sync.Mutex. Il pacchetto sync/atomic offre anche più primitive a grana fina che possono essere utilizzate per caricare/memorizzare/modificare/confrontare e scambiare le variabili atomicamente.

Problemi correlati