2

C++でpthreadを使用してスレッドセーフなキューを作成しようとしています。私のプログラムは93%の時間で動作します。他の7%の時間は、他の人がゴミを吐き出している、または眠りに落ちているようです。コンテキストスイッチがそれを壊すであろう私のキューにいくつかの欠陥があるかどうか疑問に思っていますか?

// thread-safe queue
// inspired by http://msmvps.com/blogs/vandooren/archive/2007/01/05/creating-a-thread-safe-producer-consumer-queue-in-c-without-using-locks.aspx
// only works with one producer and one consumer
#include <pthread.h>
#include <exception>

template<class T>
class tsqueue
{
    private:
        volatile int m_ReadIndex, m_WriteIndex;
        volatile T *m_Data;
        volatile bool m_Done;
        const int m_Size;
        pthread_mutex_t m_ReadMutex, m_WriteMutex;
        pthread_cond_t m_ReadCond, m_WriteCond;
    public:
        tsqueue(const int &size);
        ~tsqueue();
        void push(const T &elem);
        T pop();
        void terminate();
        bool isDone() const;
};

template <class T>
tsqueue<T>::tsqueue(const int &size) : m_ReadIndex(0), m_WriteIndex(0), m_Size(size), m_Done(false) {
    m_Data = new T[size];
    pthread_mutex_init(&m_ReadMutex, NULL);
    pthread_mutex_init(&m_WriteMutex, NULL);
    pthread_cond_init(&m_WriteCond, NULL);
    pthread_cond_init(&m_WriteCond, NULL);
}

template <class T>
tsqueue<T>::~tsqueue() {
    delete[] m_Data;
    pthread_mutex_destroy(&m_ReadMutex);
    pthread_mutex_destroy(&m_WriteMutex);
    pthread_cond_destroy(&m_ReadCond);
    pthread_cond_destroy(&m_WriteCond);
}


template <class T>
void tsqueue<T>::push(const T &elem) {
    int next = (m_WriteIndex + 1) % m_Size;
    if(next == m_ReadIndex) {
        pthread_mutex_lock(&m_WriteMutex);
        pthread_cond_wait(&m_WriteCond, &m_WriteMutex);
        pthread_mutex_unlock(&m_WriteMutex);
    }
    m_Data[m_WriteIndex] = elem;
    m_WriteIndex = next;
    pthread_cond_signal(&m_ReadCond);
}

template <class T>
T tsqueue<T>::pop() {
    if(m_ReadIndex == m_WriteIndex) {
        pthread_mutex_lock(&m_ReadMutex);
        pthread_cond_wait(&m_ReadCond, &m_ReadMutex);
        pthread_mutex_unlock(&m_ReadMutex);
        if(m_Done && m_ReadIndex == m_WriteIndex) throw "queue empty and terminated";
    }
    int next = (m_ReadIndex +1) % m_Size;
    T elem = m_Data[m_ReadIndex];
    m_ReadIndex = next;
    pthread_cond_signal(&m_WriteCond);
    return elem;
}

template <class T>
void tsqueue<T>::terminate() {
    m_Done = true;
    pthread_cond_signal(&m_ReadCond);
}

template <class T>
bool tsqueue<T>::isDone() const {
    return (m_Done && m_ReadIndex == m_WriteIndex);
}

これは次のように使用できます。

// thread 1
while(cin.get(c)) {
    queue1.push(c);
}
queue1.terminate();


// thread 2
while(!queue1.isDone()) {
    try{ c = queue1.pop(); }
    catch(char const* str){break;}
    cout.put(c);
}

誰かがこれに問題を見つけたら、そう言ってください:)

4

5 に答える 5

8

はい、ここには間違いなく問題があります。キュー メンバー変数へのすべてのアクセスは、ミューテックスの外部で発生します。実際、ミューテックスは条件変数を待機しているだけなので、ミューテックスが何を保護しているのか完全にはわかりません。

また、リーダーとライターは常にロックステップで動作し、キューが 1 要素のサイズを超えて大きくなることはないようです。

于 2009-02-12T03:07:35.813 に答える
3

これが実際のコードである場合、最初の問題の 1 つは、初期化をm_WriteCond2 回行っているにもかかわらず、まったく初期化m_ReadCondしていないことです。

于 2009-02-12T03:05:51.030 に答える
2

このクラスはモニターとして扱う必要があります。各キュー (通常のミューテックス) には「モニター ロック」が必要です。キュー内のフィールドを読み書きするメソッドに入るときはいつでも、入ったらすぐにこのミューテックスをロックする必要があります。これにより、一度に複数のスレッドがキューと対話することを防ぎます。条件を待機する前、およびメソッドを終了するときは、他のスレッドが入る可能性があるため、ロックを解放する必要があります。条件の待機が完了したら、必ずロックを再取得してください。

于 2009-02-12T03:14:44.190 に答える
1

適切なパフォーマンスを備えたものが必要な場合は、R / Wロックをダンプして、非常に単純なスピンロックを使用することを強くお勧めします。または、R / Wロックで必要なパフォーマンスが得られると本当に思っている場合は、Joe Duffyのこのデザイン(一言R / Wスピンロック)に基づいて独自にロールします。

于 2009-02-12T15:33:54.477 に答える
0

問題は、スレッド 1 が cin.get(c) を実行する前にスレッド 2 が実行できるという競合状態があることです。データが初期化されていることを確認し、データが入力されていない場合に何かをしていることを確認しているという情報を取得する必要があります。

たぶん、これが行われている残りのコードが表示されないのは私です。

于 2009-02-12T03:11:55.587 に答える