2011-02-16 4 views
6

pthreads에 문제가 있는데 어디서 교착 상태에 빠졌다고 생각합니다. 내가 생각했던 차단 대기열을 만들었지 만, 더 많은 테스트를 한 후에 blocking_queue에서 차단중인 여러 스레드를 취소하려고하면 교착 상태가 발생하는 것으로 나타났습니다.C++ pthread 블로킹 큐 데드락 (내 생각 엔)

블로킹 큐는 매우 간단하며 다음과 같습니다

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; 
} 

는 테스트를 위해,이 블록 큐 당겨 4 개 스레드를 만들었습니다. 블로킹 큐에 print 문을 추가하고 각 스레드가 pthread_cond_wait() 메서드를 사용하고 있습니다. 그러나, 각 스레드에서 pthread_cancel() 및 pthread_join()을 호출하려고하면 프로그램이 중단됩니다.

나는 하나의 스레드로 이것을 테스트했으며 완벽하게 작동합니다.

설명서에 따르면 pthread_cond_wait()은 취소 지점이므로 해당 스레드에서 cancel을 호출하면 실행이 중지되어야합니다 (단 하나의 스레드에서만 작동 함). 그러나 pthread_mutex_lock은 취소 지점이 아닙니다. pthread_cancel()이 호출 될 때 어떤 일이 벌어지면, 취소 된 쓰레드는 종료하기 전에 뮤텍스를 받아서 풀지 않으며, 다음 쓰레드가 취소되면 뮤텍스와 데드락을 얻을 수 없습니까? 아니면 제가 잘못하고있는 다른 것이 있습니다.

어떤 조언도 좋을 것입니다. 고마워요 :)

+0

봅니다 [helgrind] (http://valgrind.org/info/tools.html#helgrind), 나를 위해 과거에 유용있어 사용 경쟁 조건 및 교착 상태를 탐지하기위한 것입니다. – Flexo

+0

취소는 위험 할 수 있습니다. 논리를 좀 더 보여주세요. 작업자 스레드의 취소 가능성 상태는 무엇입니까?클린업 처리기는 무엇입니까? 정확히 어떻게 여러 스레드에서 취소/결합하라는 명령을 어떻게 처리하고 있습니까? – pilcrow

답변

5

pthread_cancel()은 피해야합니다.

거기에서 예외를 throw하여 Blocking_Queue :: pull()에서 차단 된 모든 스레드를 차단 해제 할 수 있습니다.

대기열의 약점 하나는 T t = _queue.front();이 예외를 throw 할 수있는 T의 복사본 생성자를 호출하여 뮤텍스가 영원히 대기열에 잠겼다는 것입니다. C++ 스코프 잠금을 사용하는 것이 더 좋습니다. 여기

는 우아한 스레드 종료의 예는 다음과 같습니다

$ 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

내 문제에 대한 아주 좋은 해결책입니다. 공유해 주셔서 감사합니다! :) – vimalloc

1

저는 정확히 pthread_cancel()에 익숙하지 않습니다 - 나는 협동 종료를 선호합니다.

pthread_cancel()에서 뮤텍스를 잠그지 않으시겠습니까? 취소 처리기로 정리해야한다고 가정합니다.

+0

이론적으로 pthread_cond_wait()가 호출되면 mutex가 해제되어야합니다. 다중 스레드가 pthread_cond_wait() 문을 사용하기 때문에 뮤텍스가 해제되어야합니다. 그러나 pthread_cancel이 호출 될 때 뮤텍스가 필요한 것처럼 보이지만 적어도 그것이 내 설명입니다. – vimalloc

+1

pthread_cond_wait()가 취소되면 뮤텍스가 실제로 재 획득됩니다. http://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_cond_wait.html "상태 대기 (시간 초과 여부에 관계 없음)는 취소 지점입니다 스레드의 취소 가능성 활성화 상태가 PTHREAD_CANCEL_DEFERRED로 설정되면, 조건 대기 중에 취소 요청시 작동하는 부작용은 뮤텍스가 첫 번째 취소 정리 처리기를 호출하기 전에 다시 획득된다는 것입니다. " – sstn

1

나는 pthread_cond_wait()/pthread_cancel()에서 비슷한 경험을했습니다. 스레드가 어떤 이유로 반환 된 후에도 여전히 잠금을 유지하는 데 문제가 있었으며 잠긴 것과 같은 스레드에서 잠금을 해제해야하므로 잠금을 해제 할 수 없었습니다. pthread_mutex_destroy()를 수행 할 때 이러한 오류를 발견했습니다. 단일 제작자, 단일 소비자 상황이 있었으므로 교착 상태가 발생하지 않았기 때문입니다.

pthread_cond_wait()는 반환 할 때 뮤텍스를 잠그기로되어 있는데,이 문제가 발생할 수 있지만 강제로 스레드를 취소 한 이후로 마지막 잠금을 해제하지 않았습니다. 안전을 위해서 나는 일반적으로 pthread_cancel()을 사용하지 않는 것을 시도한다. 일부 플랫폼은 이것을 지원하지 않기 때문이다. 휘발성 bool 또는 atomics를 사용하여 스레드를 종료해야하는지 확인할 수 있습니다. 그렇게하면 뮤텍스도 깨끗하게 처리됩니다.