4

次のマネージャー<->ワーカーの状況があります。

class Manager {
private:
    pthread_attr_t workerSettings;
    pthread_t worker;
    pthread_cond_t condition;
    pthread_mutex_t mutex;
    bool workerRunning;

    static void* worker_function(void* args) {
        Manager* manager = (Manager*)args;

        while(true) {
            while(true) {
                pthread_mutex_lock(&manager->mutex);
                if(/* new data available */)
                {
                    /* copy new data from shared to thread memory */
                    pthread_mutex_unlock(&manager->mutex);
                }
                else
                {
                    pthread_mutex_unlock(&manager->mutex);
                    break;
                }

                /* process the data in thread memory */

                pthread_mutex_lock(&manager->mutex);
                /* copy results back to shared memory */
                pthread_mutex_unlock(&manager->mutex);
            }

            pthread_mutex_lock(&manager->mutex);

            // wait for new data to arrive
            while(manager->workerRunning && !/* new data available*/)
                pthread_cond_wait(&manager->condition, &manager->mutex);

            // check if we should continue running
            if(!manager->workerRunning)
            {
                pthread_mutex_unlock(&manager->mutex);
                break;
            }

            pthread_mutex_unlock(&manager->mutex);
        }

        pthread_exit(NULL);
        return NULL; // just to avoid the missing return statement compiler warning
    }

public:
    Manager() : workerRunning(true) {
        pthread_cond_init(&condition, NULL);
        pthread_mutex_init(&mutex, NULL);
        pthread_attr_init(&workerSettings);
        pthread_attr_setdetachstate(&workerSettings, PTHREAD_CREATE_JOINABLE);
        pthread_create(&worker, &workerSettings, worker_function, (void*)this);
    }

    // this *may* be called repeatedly or very seldom
    void addData(void) {
        pthread_mutex_lock(&mutex);
        /* copy new data into shared memory */
        pthread_cond_signal(&condition);
        pthread_mutex_unlock(&mutex);
    }

    ~Manager()
    {
        // set workerRunning to false and signal the worker
        pthread_mutex_lock(&mutex);
        workerRunning = false;
        pthread_cond_signal(&condition);
        pthread_mutex_unlock(&mutex);

        // wait for the worker to exit
        pthread_join(worker, NULL);

        // cleanup
        pthread_attr_destroy(&workerSettings);
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&condition);
    }
};

これについては、いくつかの場所で完全にはわかりません。

  • Manager がそのコンストラクターで新しいスレッドを生成するという事実は、悪い習慣と見なされますか? (私は Manager オブジェクトを 1 つしか持たないので、それで問題ないと思います)
  • pthread_exit についてはどうですか - これは多くのチュートリアルで見られますが、なぜそこにあるのかよくわかりません。関数を返してスレッドを終了することはできませんか? また、リターン NULL はデッド コードだと思いますが、gcc は、その時点で pthread_exit が既にスレッドを強制終了したことを明らかに認識できないため、欠落している場合に警告します。
  • コンストラクターについて - スレッドを生成した直後にスレッド属性オブジェクト (workerSettings) を破棄できますか、それともスレッドの存続期間全体にわたって有効である必要がありますか?
  • デストラクタについて: これは正しい方法ですか?

最も重要な:

  • あなたの経験豊富な目で同期の問題が見られますか?

ご協力いただきありがとうございます!

4

2 に答える 2

3

あなたが尋ねる...

Manager がそのコンストラクターで新しいスレッドを生成するという事実は、悪い習慣と見なされますか?

ほとんどの場合、オブジェクトの作成とリソースの取得には RAII で十分です。場合によっては、遅延リソースの初期化を実現したい場合があります。最初にオブジェクトを構築し、後で初期化を続行する場合です。これは、たとえば、ctor (デフォルトまたはパラメーター化) および open/start ルーチンを介して実現できます。ctor でそれを行うこともできますが、(演算子 new を介して) プロセス ヒープにオブジェクトを割り当てることで、遅延オブジェクトの作成を実現できます。それは、要件、ソフトウェア設計の考慮事項、および企業のソフトウェア開発基準によって異なります。そのため、ctor でスレッドを作成するか、アプリケーション/オブジェクト ライフサイクルの後の段階でスレッドを生成する必要がある場合があります。

pthread_exit はどうですか

必須ではありません。呼び出しスレッドを終了し、その終了ステータスを待機中のスレッドが利用できるようにします (つまり、pthread_join() を介して)。pthread_exit() への暗黙の呼び出しは、スレッドが開始ルーチンから戻るときに発生します。基本的に、pthread_exit() 関数は、exit() に似たインターフェースを提供しますが、スレッドごとに (キャンセルのクリーンアップ ハンドラーを含む) 使用します。ただし、キャンセル クリーンアップ ハンドラから、または TSD (スレッド固有のデータ領域) に割り当てられたオブジェクトのデストラクタから pthread_exit() を呼び出すことに注意してください。望ましくない副作用が発生する可能性があります。

コンストラクターについて - スレッドを生成した直後にスレッド属性オブジェクト (workerSettings) を破棄できますか、それともスレッドの存続期間全体にわたって有効である必要がありますか?

はい、すぐに破棄できます。既に作成されたスレッドには影響しません。

デストラクタについて: これは正しい方法ですか?

ctor の場合と同じです: dtor と close/stop ルーチンを使用するか、dtor ですべて実行できます: 特定のニーズ (オブジェクトの再利用性など) によって異なります。dtor がスローされないようにするだけです。

あなたの経験豊富な目で同期の問題が見られますか?

pthread_testcancel() を使用してスレッドに明示的なキャンセル ポイントを導入し、制御スレッドで pthread_cancel() + pthread_join() (PTHREAD_CANCELED を返す必要があります) を発行して、同期変数 workerRunning の代わりに子スレッドを停止することをお勧めします。もちろん、それがあなたの場合に当てはまる場合。

于 2012-06-26T10:44:32.260 に答える
2

戻ってきたらすぐに新しいデータを確認し、新しいデータpthread_cond_waitがない場合はもう一度待つ必要があります。これは、偽の wake が発生した場合に発生する可能性があり (階段から重いものを落として、カーネルが誤って目を覚ましたと考えてください) workerWaiting、ミューテックスを変更してからロックを解除して再ロックするのを 2 回繰り返してから再度待機するよりも、すぐに待機することをお勧めします。 .

RAII ロック タイプを使用すると、コードが非常にきれいになります。

    while(true) {
        while(true) {
            {
                scoped_lock l(&manager->mutex);
                if(/* new data available */)
                {
                    /* copy new data from shared to thread memory */
                }
                else
                    break;
            }

            /* process the data in thread memory */

            scoped_lock l(&manager->mutex);
            /* copy results back to shared memory */
        }

        scoped_lock l(&manager->mutex);
        // check if we should continue running
        if(!manager->workerRunning)
            break;

        // wait for new data to arrive
        manager->workerWaiting = true;
        while (!/* new data available */)
            pthread_cond_wait(&manager->condition, &manager->mutex);
        manager->workerWaiting = false;
    }

オレグが示唆するように使用pthread_cancelすると、さらに簡素化されます。

スプリアス ウェイクアップを処理するためのコードの編集に続いて、RAII を使用して再構築すると、はるかに簡単になります。

    while(true)
    {
        {
            scoped_lock l(&manager->mutex);
            // wait for new data to arrive
            while(manager->workerRunning && !/* new data available*/)
                pthread_cond_wait(&manager->condition, &manager->mutex);

            // check if we should continue running
            if(!manager->workerRunning)
                break;

            /* copy new data from shared to thread memory */
        }

        /* process the data in thread memory */

        scoped_lock l(&manager->mutex);
        /* copy results back to shared memory */
    }
    return NULL;

scoped_lock のようなものがない場合、/* copy new data from shared to thread memory */または/* process the data in thread memory */が例外をスローするとどうなりますか? ミューテックスのロックを解除することはありません。

RAII タイプは次のように単純です。

struct scoped_lock {
  explicit scoped_lock(pthrad_mutex_t* m) : mx(m) {
    pthread_mutex_lock(mx);
  }
  ~scoped_lock() { pthread_mutex_unlock(mx); }
private:
  pthread_mutex_t* mx;
  scoped_lock(const scoped_lock&);
  scoped_lock operator=(const scoped_lock&);
};
于 2012-06-26T15:43:03.497 に答える