2011-09-26 16 views
8

In alcuni punti ho visto persone creare un pool di thread e creare thread ed eseguire una funzione con tali thread. Durante il richiamo della funzione boost :: mutex viene passato per riferimento. Perché è fatto così? Credo che tu possa avere un mutex dichiarato nella stessa funzione chiamata o che possa essere dichiarato membro della classe o globale. Qualcuno può spiegare per favore?Perché passare il mutex come parametro a una funzione chiamata da un thread?

ad es.

myclass::processData() 
    { 
     boost::threadpool::pool pool(2); 
     boost::mutex mutex; 

     for (int i =0; data<maxData; ++data) 
      pool.schedule(boost::bind(&myClass::getData, boost_cref(*this), boost::ref(mutex))); 
    } 

Poi,

myClass::getData(boost::mutex& mutex) 
    { 
     boost::scoped_lock(mutex) // Why can't we have class member variable mutex or          
             //local mutex here 
     //Do somethign Here 
} 
+2

È necessario aggiungere un esempio particolare in cui viene utilizzato. Probabilmente è dovuto alla semantica implementata nell'esempio e non è possibile rispondere a tutto il contesto (a meno che non si voglia generico * solo perché * o * potrebbe avere senso * risposte) –

risposta

10

mutex di sono oggetti non copiabili, e mentre possono essere membri di una classe, sarebbe notevolmente complicare copia-la capacità di una classe genitore. Pertanto, un metodo preferito, nel caso in cui un numero di istanze di classe debba condividere gli stessi dati, sarebbe quello di creare il mutex come membro di dati statici. Altrimenti se il mutex doveva essere bloccato solo all'interno di un'istanza della classe stessa, è possibile creare un puntatore a un mutex come membro dati non statico e quindi ogni copia della classe avrebbe il proprio mutex assegnato dinamicamente (e rimangono copiabili se questo è un requisito).

Nell'esempio di codice sopra, ciò che sta avvenendo in pratica è che è stato passato un mutex globale nel pool di thread per riferimento. Ciò consente a tutti i thread che condividono le stesse posizioni di memoria di creare un blocco esclusivo su quella memoria utilizzando lo stesso mutex esatto, ma senza il sovraccarico di dover gestire l'aspetto non copiabile del mutex stesso. Il mutex in questo esempio di codice poteva anche essere un membro statico della classe myClass piuttosto che un mutex globale passato per riferimento, in quanto ogni thread avrebbe bisogno di bloccare parte della memoria accessibile globalmente da ogni thread.

Il problema con un mutex locale è che è solo una versione localmente accessibile del mutex ... quindi quando un thread blocca il mutex per condividere alcuni dati accessibili a livello globale, i dati stessi non sono protetti, poiché ogni altro thread avrà il proprio mutex locale che può essere bloccato e sbloccato. Sconfigge l'intero punto dell'esclusione reciproca.

+1

Questo non ha senso, ci sono molte applicazioni dove avere mutex essere membri non statici ha senso. Molto più spesso che avere il mutex come membro statico, e non vi è alcun motivo per non memorizzare un puntatore o un riferimento come membro. Il mutex deve essere al livello dei dati che vengono protetti (cioè se protegge i dati statici, deve essere statico, se protegge gli attributi dei membri, quindi probabilmente dovrebbe essere un membro, se è condiviso da istanze diverse o differenti tipi, quindi deve essere ancora più generale –

+0

Scusate, non volevo dire che * non posso * farlo ... Stavo solo affermando che se lo rendete un membro dati non statico, creando un la classe copiabili è molto più difficile (alla fine non sarà realmente copiabile, dovresti memorizzare un puntatore su un mutex e quindi rilasciarlo e riallocare il mutex nella copia, che non è una copia "vera" di ogni membro di la classe). Riesaminerò la mia risposta per renderlo più chiaro. – Jason

+2

... tutto dipende da cosa è la definizione del tuo oggetto (semanticamente) Nella maggior parte dei casi il mutex non fa parte dello stato dell'oggetto, ma è un'utilità per garantire che lo stato reale della classe non possa essere visto in un intermediario iate errato (invarianti interrotto) stato quando usato in un ambiente multithread. –

0

L'utilizzo del mutex locale è errato: il pool di thread può richiamare più istanze di funzione e devono funzionare con lo stesso mutex. Il membro della classe è OK. Il passaggio di mutex alla funzione lo rende più generico e leggibile. Il chiamante può decidere quale mutex passare: membro della classe o qualsiasi altra cosa.

+0

Questo mi fa male. Se spetta alla classe chiamante come usare il Mutex, non dovrebbe fare parte della definizione dell'interfaccia? – kenny

1

Credo che si possa avere un mutex dichiarato nella funzione chiamata stessa o può essere dichiarato membro della classe o globale. Qualcuno può spiegare per favore?

creazione di un nuovo mutex alla voce non protegge nulla.

se si stava considerando di dichiarare un mutex statico (o globale) per proteggere i membri non statici, quindi si può anche scrivere il programma come un programma a thread singolo (ok, ci sono alcuni casi d'angolo). un blocco statico bloccherebbe tutti i thread tranne uno (assumendo contest); equivale a "un massimo di un thread può operare nel corpo di questo metodo contemporaneamente". dichiarare un mutex statico per proteggere i dati statici va bene. come David Rodriguez - dribeas lo ha scritto succintamente nei commenti di un'altra risposta: "Il mutex dovrebbe essere al livello dei dati che vengono protetti".

si possibile dichiara una variabile membro per esempio, che potrebbe assumere la forma generalizzata:

class t_object { 
public: 
    ... 
    bool getData(t_data& outData) { 
     t_lock_scope lock(this->d_lock); 
     ... 
     outData.set(someValue); 
     return true; 
    } 

private: 
    t_lock d_lock; 
}; 

questo approccio va bene, e in alcuni casi ideale. ha senso nella maggior parte dei casi quando si costruisce un sistema in cui le istanze intendono astrarre meccanismi di blocco ed errori dai propri clienti. uno svantaggio è che può richiedere più acquisizioni e in genere richiede meccanismi di blocco più complessi (ad esempio rientranti). da più acquisizioni: il cliente può sapere che un'istanza è utilizzata in un solo thread: perché bloccare in quel caso? così, un mucchio di piccoli metodi di sicurezza introdurranno un sacco di spese generali. con il blocco, si desidera entrare e uscire dalle aree protette appena possibile (senza introdurre molte acquisizioni), quindi le sezioni critiche sono spesso operazioni più grandi di quelle tipiche.

se l'interfaccia pubblica richiede questo blocco come argomento (come si vede nel tuo esempio), è un segnale che il disegno può essere semplificata privatizzando bloccaggio (rendendo la funzione dell'oggetto in modo sicuro filo, invece di passare la serratura come risorsa tenuta esternamente).

utilizzando un blocco esterno (o associato o associato), è possibile ridurre le acquisizioni (o il tempo totale bloccato). questo approccio consente anche di aggiungere il blocco a un'istanza dopo il fatto. consente inoltre al client di configurare il funzionamento del blocco. il client può utilizzare un numero inferiore di blocchi condividendoli (tra una serie di istanze). anche un semplice esempio di composizione può illustrare questo (supporto entrambi i modelli):

class t_composition { 
public: 
    ... 
private: 
    t_lock d_lock; // << name and data can share this lock 
    t_string d_name; 
    t_data d_data; 
}; 

considerando la complessità di alcuni sistemi multithread, spingendo la responsabilità del corretto bloccaggio sul client può essere un pessima idea.

entrambi i modelli (vincolati e come variabile membro) possono essere utilizzati efficacemente. che è meglio in un dato scenario varia a seconda del problema.

+0

Grazie Justin. Ma vuol dire che se vuoi proteggere i dati statici hai bisogno di un mutex statico e se vuoi proteggere i membri dei dati hai bisogno di mutex di livello di classe come regola generale? – polapts

+0

come approccio * generale *: sì, questo è uno dei molteplici approcci utilizzabili per garantire che le implementazioni siano thread-safe (un altro è l'approccio nel PO). la granularità è anche importante se si desidera ridurre al minimo le acquisizioni e i tempi di blocco. un blocco di livello di classe può essere troppo piccolo in alcuni casi, questo porterà a molte acquisizioni non necessarie. un blocco più grande (globale o basato su documenti) non sarà ideale in molti casi perché il tempo bloccato per acquisizione aumenta e c'è una probabilità molto più alta che altri attenderanno durante quel periodo. – justin

Problemi correlati