2013-04-05 11 views
5

Ho due thread cercando di bloccare lo stesso boost::mutex. Uno di questi thread elabora continuamente alcuni dati e l'altro visualizza periodicamente lo stato corrente. Il thread di elaborazione, secondo la mia intenzione, rilascia il blocco molto frequentemente e riacquisisce, in modo che il filo display può toccare e acquisire ogni volta ne ha bisogno. Quindi, ovviamente, mi piacerebbe che il thread di visualizzazione acquisisse il blocco la prossima volta che viene rilasciato dal thread di processo. Tuttavia, non lo fa, invece, attende il blocco e lo acquisisce solo dopo molti cicli di blocco dal thread del processo.blocco Acquisisci non appena è disponibile

Si prega di ispezionare l'esempio minimo che illustra il mio problema:

#include <boost/thread.hpp> 
#include <iostream> 

using namespace std; 
using namespace boost; 

mutex mut; 

void process() { 
     double start = time(0); 
     while(1) { 
       unique_lock<mutex> lock(mut); 
       this_thread::sleep(posix_time::milliseconds(10)); 
       std::cout<<"."; 
       if(time(0)>start+10) break; 
     } 
} 

int main() { 

     thread t(process); 

     while(!t.timed_join(posix_time::seconds(1))) { 
       posix_time::ptime mst1 = posix_time::microsec_clock::local_time(); 
       cout<<endl<<"attempting to lock"<<endl; 
       cout.flush(); 

       unique_lock<mutex> lock(mut); 

       posix_time::ptime mst2 = posix_time::microsec_clock::local_time(); 
       posix_time::time_duration msdiff = mst2 - mst1; 
       cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl; 
       cout.flush(); 
     } 

} 

Compilato con: g++ mutextest.cpp -lboost_thread -pthread

Quando eseguo il file eseguibile, un esempio di output è come questo:

................................................................................................... 
attempting to lock 
.................................................................................................................................................................................................................................................................................................................................................................................................................................... 
acquired lock in: 4243 
................................................................................................... 
attempting to lock 
........................................................................................................ 
acquired lock in: 1049 
................................................................................................... 
attempting to lock 
........................................................................................................................ 
acquired lock in: 1211 
.................................... 

Come si può vedere, nel peggiore dei casi, il thread del display attende 424 cicli di rilascio del blocco prima che torni a prendere il lucchetto.

Sono ovviamente utilizzando il mutex in modo sbagliato, ma qual è il solito modo per risolvere questo problema?

+0

accanto soliti sospetti: (variabile di condizione, resa,) prova un contenitore sicuro per i thread ... – NoSenseEtAl

risposta

0

Ho risolto il problema utilizzando condizioni, come suggerito da Dale Wilson un FKaria, ma ho usato nella direzione opposta. Quindi, il thread di processo controlla un indicatore di pausa e, quando è impostato, attende una condizione, quindi rilasciando il blocco. Il thread di visualizzazione controlla il flag di pausa e riprende anche il thread del processo notificandolo attraverso la condizione. Codice: (il codice è in gran parte la stessa, ho segnato le nuove linee con //New)

#include <boost/thread.hpp> 
#include <boost/thread/condition.hpp> 
#include <iostream> 

using namespace std; 
using namespace boost; 

mutex mut; 
condition cond; 

volatile bool shouldPause = false; //New 

void process() { 
     double start = time(0); 
     while(1) { 
       unique_lock<mutex> lock(mut); 

       if(shouldPause) cond.wait(mut); //New 

       this_thread::sleep(posix_time::milliseconds(10)); 
       std::cout<<"."; 
       if(time(0)>start+10) break; 
     } 
} 

int main() { 

     thread t(process); 

     while(!t.timed_join(posix_time::seconds(1))) { 
       posix_time::ptime mst1 = posix_time::microsec_clock::local_time(); 
       cout<<endl<<"attempting to lock"<<endl; 
       cout.flush(); 
       shouldPause = true; // New 
       unique_lock<mutex> lock(mut); 

       posix_time::ptime mst2 = posix_time::microsec_clock::local_time(); 
       posix_time::time_duration msdiff = mst2 - mst1; 
       cout << std::endl<<"acquired lock in: "<<msdiff.total_milliseconds() << endl; 
       cout.flush(); 

       shouldPause = false; // New 
       cond.notify_all(); // New 
     } 

} 

Ora l'uscita è proprio come vorrei che fosse:

................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 8 
................................................................................................... 
attempting to lock 
. 
acquired lock in: 9 
.......................... 
4

Non è che si sta utilizzando il mutex sbagliato, solo che la filettatura non fa quello che ci si aspetta qui. Il sistema operativo decide che corre discussione quando (questo è noto come "programmazione"), e non c'è niente nel codice che impone un interruttore filo alla fine del ciclo; il filo continua e riacchia la serratura. Una cosa da provare è quello di aggiungere una chiamata a this_thread::yield() dopo il rilascio del blocco (in questo caso, nella parte superiore del ciclo, prima di ribloccare); ciò suggerirà allo scheduler che è appropriato che venga eseguito un altro thread. Se vuoi veramente un interleaving strettamente sincronizzato, i thread non lo faranno; invece, scrivi una funzione di livello superiore che chiama le tue due funzioni una dopo l'altra.

+0

'yield()' sembra essere un po 'd'aiuto, ma è ancora male. Con 'yield()' prima della costruzione 'unique_lock' nel thread di elaborazione, vedo ancora fino a ~ 300 iterazioni di blocco prima che il thread di visualizzazione catturi il lock. Non voglio un interleaving super stretto perché è solo un display, ma sarei grato se non aspettasse le 300 iterazioni folli :) – enobayram

+0

Yuk. Prova a chiamare 'sleep'; quando un thread dorme, qualsiasi SO ragionevole eseguirà qualche altro thread. –

+0

Ho provato anche quello, se dormi per 0 millisecondi, che è come se il sonno non esistesse, altrimenti puoi dormire almeno per 10 millisecondi e questo è semplicemente troppo da sprecare per una tale seccatura:/intendo , non è che sto chiedendo un'impossibilità teorica, la serratura che concede l'implementazione ha semplicemente bisogno di darla al primo candidato. – enobayram

1

Se il filo aggiornamento non ha nulla altro da fare si può attendere che il mutex per diventare disponibili.

sguardo al boost :: condition_variable. Si può leggere su qui http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html e qui Using boost condition variables

Se avere il filo aggiornamento andare a dormire sarebbe un problema - è su molti sistemi GUI e non si specifica quale si sta utilizzando - considera l'invio di un messaggio dal thread di elaborazione al thread di aggiornamento. (Ancora una volta i dettagli dipendono dalla vostra piattaforma.) Il messaggio può contenere sia le informazioni necessarie per aggiornare la visualizzazione, o essere una notifica che "ora sarebbe un buon momento per guardare."

Se si utilizza un codice di condizione, il thread di elaborazione dovrebbe probabilmente cedere dopo la segnalazione della condizione e prima di riacquistare la serratura per aprire una grande finestra per il thread di aggiornamento.

+0

Si noti che il thread di aggiornamento è già in attesa che il mutex diventi disponibile, ma il problema è che non acquisisce il blocco per un tempo molto lungo, anche dopo che è disponibile. Se il sistema GUI è veramente importante, il mio thread di visualizzazione usa OpenGL per fare un po 'di disegno sullo schermo. – enobayram

+0

La thread di elaborazione che invia un messaggio al thread di aggiornamento non sarà di aiuto. Il thread del display non può "dare un'occhiata" senza acquisire il blocco, ed è qui che il problema è comunque. – enobayram

1

Si consiglia di dare un'occhiata a boost::condition_variable, specialmente ai metodi wait() e notify_one() o notify_all(). Il metodo wait() bloccherà il thread corrente fino a quando non verrà rilasciato il blocco. I metodi notify_one() e notify_all() notificare una o tutte le discussioni che sono in attesa del blocco per continuare l'esecuzione.

+0

Ho già provato 'condition_variable' ma non funziona. I metodi 'notify_x' portano gli altri thread in uno stato" pronto ", ma non impongono un risveglio. Ad ogni modo, hai bisogno del lucchetto in primo luogo prima di aspettare, il mio problema non è riuscire ad acquisire il lucchetto. – enobayram

1

come la vedo io, il principio di mutex non è l'equità, ma la correttezza. Gli stessi mutex non possono controllare lo scheduler.Una cosa che mi dà fastidio è il motivo per cui hai scelto di creare 2 thread, che utilizzano un blocco con grana grossa. In teoria stai eseguendo due cose in parallelo, ma di fatto le stai rendendo interdipendenti/seriali.

L'idea di Pete sembra molto più bella (una funzione che esegue questi aggiornamento &) e si possono ancora utilizzare i thread all'interno di ciascuna delle funzioni interne, senza doversi preoccupare tanto della contesa e della correttezza.

Se davvero si desidera avere due thread in esecuzione in parallelo, allora vi posso dare solo alcuni suggerimenti:

  • Spinlock utilizzando Atomics e dimenticare mutex,
  • andare in terra implementazione definita e imposta le priorità del filo, oppure
  • rendere il blocco molto più a grana fine.

Purtroppo nessuno di questi è a prova di problemi.

+0

Grazie per i vostri suggerimenti, hanno tutti casi utili, ma non i miei: uno spinlock è semplicemente troppo dispendioso, voglio rimanere portatile e il blocco è a grana fine quanto teoricamente potrebbe essere. – enobayram

+0

Come per unirli in una singola funzione; Posso vedere che sarebbe un buon approccio per l'esempio minimo, ma la vera applicazione è molto più complicata. inoltre, sto facendo anche altre cose nel thread del display. Estraggo le informazioni necessarie dal modello del mondo e rilascio il blocco mentre il thread del display gestisce le chiamate OpenGL. – enobayram

+0

Infine, sto anche usando il modello a più thread come un modo di astrazione, semplifica davvero il mio design. – enobayram