2011-02-16 5 views
6

Ho un problema con i pthreads in cui penso di ottenere un deadlock. Ho creato una coda di blocco che pensavo stesse funzionando, ma dopo aver fatto un po 'di test ho scoperto che se provo e annullo più thread che bloccano il blocking_queue, mi sembra di ottenere un deadlock.deadlock di blocco di C++ pthread deadlock (credo)

La coda di blocco è molto semplice e si presenta così:

template <class T> class Blocking_Queue 
{ 
public: 
    Blocking_Queue() 
    { 
     pthread_mutex_init(&_lock, NULL); 
     pthread_cond_init(&_cond, NULL); 
    } 

    ~Blocking_Queue() 
    { 
     pthread_mutex_destroy(&_lock); 
     pthread_cond_destroy(&_cond); 
    } 

    void put(T t) 
    { 
     pthread_mutex_lock(&_lock); 
     _queue.push(t); 
     pthread_cond_signal(&_cond); 
     pthread_mutex_unlock(&_lock); 
    } 

    T pull() 
    { 
     pthread_mutex_lock(&_lock); 
     while(_queue.empty()) 
     { 
      pthread_cond_wait(&_cond, &_lock); 
     } 

     T t = _queue.front(); 
     _queue.pop(); 

     pthread_mutex_unlock(&_lock); 

     return t; 
    } 

priavte: 
    std::queue<T> _queue; 
    pthread_cond_t _cond; 
    pthread_mutex_t _lock; 
} 

Per il test, ho creato 4 thread che tirano su questa coda di blocco. Ho aggiunto alcune istruzioni di stampa alla coda di blocco e ogni thread sta per arrivare al metodo pthread_cond_wait(). Tuttavia, quando provo a chiamare pthread_cancel() e pthread_join() su ogni thread il programma si blocca.

Ho anche provato questo con un solo thread e funziona perfettamente.

In base alla documentazione, pthread_cond_wait() è un punto di annullamento, quindi l'annullamento di chiamata su quei thread dovrebbe causare l'interruzione dell'esecuzione (e questo funziona con solo 1 thread). Tuttavia pthread_mutex_lock non è un punto di cancellazione. Potrebbe succedere qualcosa sulla falsariga di quando si chiama pthread_cancel(), il thread cancellato acquisisce il mutex prima di terminare e non lo sblocca, e poi quando il thread successivo viene annullato non può acquisire il mutex e deadlock? O c'è qualcos'altro che sto sbagliando.

Qualsiasi consiglio sarebbe bello. Grazie :)

+0

provare a utilizzare [Helgrind] (http://valgrind.org/info/tools.html#helgrind), è stato utile in passato per me per individuare condizioni di gara e deadlock. – Flexo

+0

L'annullamento può essere ingannevole. Per favore mostraci più della tua logica: qual è lo stato di cancellabilità dei tuoi thread di lavoro?Quali addetti alle pulizie? E esattamente come stai sequenziando le chiamate per annullare/partecipare su più thread? – pilcrow

risposta

5

pthread_cancel() è meglio evitare.

È possibile sbloccare tutti i thread bloccati su Blocking_Queue :: pull() generando un'eccezione da lì.

Un punto debole nella coda è che T t = _queue.front(); richiama il costruttore di copie di T che può generare un'eccezione, rendendo la coda mutex bloccata per sempre. Meglio usare i blocchi con scope C++.

Ecco un esempio di terminazione filo aggraziata:

$ cat test.cc 
#include <boost/thread/mutex.hpp> 
#include <boost/thread/thread.hpp> 
#include <boost/thread/condition_variable.hpp> 
#include <exception> 
#include <list> 
#include <stdio.h> 

struct BlockingQueueTerminate 
    : std::exception 
{}; 

template<class T> 
class BlockingQueue 
{ 
private: 
    boost::mutex mtx_; 
    boost::condition_variable cnd_; 
    std::list<T> q_; 
    unsigned blocked_; 
    bool stop_; 

public: 
    BlockingQueue() 
     : blocked_() 
     , stop_() 
    {} 

    ~BlockingQueue() 
    { 
     this->stop(true); 
    } 

    void stop(bool wait) 
    { 
     // tell threads blocked on BlockingQueue::pull() to leave 
     boost::mutex::scoped_lock lock(mtx_); 
     stop_ = true; 
     cnd_.notify_all(); 

     if(wait) // wait till all threads blocked on the queue leave BlockingQueue::pull() 
      while(blocked_) 
       cnd_.wait(lock); 
    } 

    void put(T t) 
    { 
     boost::mutex::scoped_lock lock(mtx_); 
     q_.push_back(t); 
     cnd_.notify_one(); 
    } 

    T pull() 
    { 
     boost::mutex::scoped_lock lock(mtx_); 

     ++blocked_; 
     while(!stop_ && q_.empty()) 
      cnd_.wait(lock); 
     --blocked_; 

     if(stop_) { 
      cnd_.notify_all(); // tell stop() this thread has left 
      throw BlockingQueueTerminate(); 
     } 

     T front = q_.front(); 
     q_.pop_front(); 
     return front; 
    } 
}; 

void sleep_ms(unsigned ms) 
{ 
    // i am using old boost 
    boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(ms)); 
    // with latest one you can do this 
    //boost::thread::sleep(boost::posix_time::milliseconds(10)); 
} 

void thread(int n, BlockingQueue<int>* q) 
try 
{ 
    for(;;) { 
     int m = q->pull(); 
     printf("thread %u: pulled %d\n", n, m); 
     sleep_ms(10); 
    } 
} 
catch(BlockingQueueTerminate&) 
{ 
    printf("thread %u: finished\n", n); 
} 

int main() 
{ 
    BlockingQueue<int> q; 

    // create two threads 
    boost::thread_group tg; 
    tg.create_thread(boost::bind(thread, 1, &q)); 
    tg.create_thread(boost::bind(thread, 2, &q)); 
    for(int i = 1; i < 10; ++i) 
     q.put(i); 
    sleep_ms(100); // let the threads do something 
    q.stop(false); // tell the threads to stop 
    tg.join_all(); // wait till they stop 
} 

$ g++ -pthread -Wall -Wextra -o test -lboost_thread-mt test.cc 

$ ./test 
thread 2: pulled 1 
thread 1: pulled 2 
thread 1: pulled 3 
thread 2: pulled 4 
thread 1: pulled 5 
thread 2: pulled 6 
thread 1: pulled 7 
thread 2: pulled 8 
thread 1: pulled 9 
thread 2: finished 
thread 1: finished 
+0

Questa è una soluzione molto bella al mio problema, grazie per averlo condiviso! :) – vimalloc

1

Non ho esattamente familiarità con pthread_cancel() - Preferisco la terminazione cooperativa.

Non sarebbe un pthread_cancel() lasciare il mutex bloccato? Suppongo che hai bisogno di ripulire con un gestore di cancellazione.

+0

In teoria quando viene chiamato pthread_cond_wait(), il mutex deve essere rilasciato (e lo è, poiché più thread lo portano all'istruzione pthread_cond_wait()). Tuttavia, quando viene chiamato pthread_cancel, sembra che il mutex sia richiesto, almeno questa è la mia spiegazione. – vimalloc

+1

Il mutex shall in realtà deve essere riacquisito (se pthread_cond_wait() viene cancellato). Vedi http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_cond_wait.html "L'attesa di una condizione (programmata o meno) è un punto di annullamento Quando lo stato di abilitazione della cancellazione di un thread è impostato su PTHREAD_CANCEL_DEFERRED, un effetto collaterale di agire su una richiesta di cancellazione mentre in una condizione di attesa è che il mutex è (in effetti) ri-acquisito prima di chiamare il primo gestore di cleanup di cancellazione. " – sstn

1

Ho avuto un'esperienza simile con pthread_cond_wait()/pthread_cancel(). Ho avuto problemi con un blocco ancora in attesa dopo il thread restituito per qualche motivo, ed era impossibile sbloccarlo, dal momento che è necessario sbloccare nello stesso thread bloccato. Ho notato questi errori durante l'esecuzione di pthread_mutex_destroy() poiché avevo un singolo produttore, una situazione di singolo consumatore, quindi il deadlock non si verificava.

pthread_cond_wait() si suppone che blocchi il mutex al momento del ritorno, e questo potrebbe essere successo, ma lo sblocco finale non è stato eseguito poiché abbiamo forzatamente annullato il thread. Per sicurezza, in genere cerco di evitare di utilizzare pthread_cancel() del tutto poiché alcune piattaforme non lo supportano nemmeno. Potresti usare un bool o un atomico volatile e controllare se il thread deve essere spento. In questo modo, i mutex verranno gestiti in modo pulito.