web-dev-qa-db-ja.com

std :: condition_variableで待機しているC ++ 11 std :: threadsの停止

新しいC++ 11標準の基本的なマルチスレッド化メカニズムを理解しようとしています。私が考えることができる最も基本的な例は次のとおりです:

  • プロデューサーとコンシューマーは別々のスレッドで実装されます
  • プロデューサーはキューに一定量のアイテムを配置します
  • 存在する場合、消費者はキューからアイテムを取得します

この例は、マルチスレッドに関する多くの教科書でも使用されており、通信プロセスに関するすべてが正常に機能します。ただし、コンシューマスレッドの停止に関しては問題があります。

明示的な停止信号を受け取るまでコンシューマーを実行させたい(ほとんどの場合、これはプロデューサーが完了するのを待って、プログラムが終了する前にコンシューマーを停止できることを意味します)。残念ながら、C++ 11スレッドには割り込みメカニズムがありません(これは、Javaなどのマルチスレッドから知っています)。したがって、isRunningなどのフラグを使用して、スレッドが必要であることを通知する必要があります止まる。

主な問題は次のとおりです。プロデューサースレッドを停止した後、キューは空になり、コンシューマーは_condition_variable_でキューが再びいっぱいになったときにシグナルを受け取るのを待っています。したがって、終了する前に変数でnotify_all()を呼び出してスレッドをウェイクアップする必要があります。

私は実用的な解決策を見つけましたが、それは何とか厄介なようです。コードの例を以下に示します(申し訳ありませんが、「最小限の」最小限の例では、コードサイズをさらに縮小できませんでした)。

Queueクラス:

_class Queue{
public:
    Queue() : m_isProgramStopped{ false } { }

    void Push(int i){
        std::unique_lock<std::mutex> lock(m_mtx);
        m_q.Push(i);
        m_cond.notify_one();
    }

    int pop(){
        std::unique_lock<std::mutex> lock(m_mtx);
        m_cond.wait(lock, [&](){ return !m_q.empty() || m_isProgramStopped; });

        if (m_isProgramStopped){
            throw std::exception("Program stopped!");
        }

        int x = m_q.front();
        m_q.pop();

        std::cout << "Thread " << std::this_thread::get_id() << " popped " << x << "." << std::endl;
        return x;
    }

    void stop(){
        m_isProgramStopped = true;
        m_cond.notify_all();
    }

private:
    std::queue<int> m_q;
    std::mutex m_mtx;
    std::condition_variable m_cond;
    bool m_isProgramStopped;
};
_

プロデューサー:

_class Producer{
public:
    Producer(Queue & q) : m_q{ q }, m_counter{ 1 } { }

    void produce(){
        for (int i = 0; i < 5; i++){
            m_q.Push(m_counter++);
            std::this_thread::sleep_for(std::chrono::milliseconds{ 500 });
        }
    }

    void execute(){
        m_t = std::thread(&Producer::produce, this);
    }

    void join(){
        m_t.join();
    }

private:
    Queue & m_q;
    std::thread m_t;

    unsigned int m_counter;
};
_

消費者:

_class Consumer{
public:
    Consumer(Queue & q) : m_q{ q }, m_takeCounter{ 0 }, m_isRunning{ true }
    { }

    ~Consumer(){
        std::cout << "KILL CONSUMER! - TOOK: " << m_takeCounter << "." << std::endl;
    }

    void consume(){
        while (m_isRunning){
            try{
                m_q.pop();
                m_takeCounter++;
            }
            catch (std::exception e){
                std::cout << "Program was stopped while waiting." << std::endl;
            }
        }
    }

    void execute(){
        m_t = std::thread(&Consumer::consume, this);
    }

    void join(){
        m_t.join();
    }

    void stop(){
        m_isRunning = false;
    }

private:
    Queue & m_q;
    std::thread m_t;

    unsigned int m_takeCounter;
    bool m_isRunning;
};
_

そして最後にmain()

_int main(void){
    Queue q;

    Consumer cons{ q };
    Producer prod{ q };

    cons.execute();
    prod.execute();

    prod.join();

    cons.stop();
    q.stop();

    cons.join();

    std::cout << "END" << std::endl;

    return EXIT_SUCCESS;
}
_

これは、条件変数を待機しているスレッドを終了するright方法ですか、それともより優れたメソッドがありますか?現在、キューはプログラムが停止したかどうかを知る必要があり(私の意見では、コンポーネントの疎結合を破壊します)、正しくないようにキューでstop()を明示的に呼び出す必要があります。

さらに、キューが空の場合に信号として使用する必要がある条件変数は、プログラムが終了した場合、別の条件を表します。私が間違っていない場合、スレッドが何らかのイベントの発生を条件変数で待機するたびに、実行を続行する前にスレッドを停止する必要があるかどうかもチェックする必要があります(これも間違っているようです)。

デザイン全体に欠陥があるため、これらの問題が発生しますか、それともスレッドを正常に終了するために使用できるメカニズムが不足していますか?

32
Devon Cornwall

いいえ、設計には何の問題もありません。これは、この種の問題に対して採用される通常のアプローチです。

条件変数に複数の条件(たとえば、キュ​​ー上の何かまたはプログラムの停止)をアタッチすることは完全に有効です。重要なことは、waitが戻るときに条件のビットがチェックされることです。

Queueにフラグがあり、プログラムが停止していることを示す代わりに、フラグを「受け入れることができる」と考える必要があります。これはより良いパラダイムであり、マルチスレッド環境でよりよく機能します。

また、誰かがそれを呼び出してpopが呼び出された場合にstopに例外をスローさせる代わりに、trueを返すbool try_pop(int &value)でメソッドを置き換えることができます値が返された場合はfalse。このようにして、呼び出し側は失敗をチェックして、キューが停止しているかどうかを確認できます(bool is_stopped() constメソッドを追加)。例外処理はここで機能しますが、それはやや重い処理であり、実際にはマルチスレッドプログラムでは例外的なケースではありません。

6
Sean

waitはタイムアウトで呼び出すことができます。制御がスレッドに戻り、stopを確認できます。その値に応じて、消費するアイテムをwait実行したり、実行を終了したりできます。 c ++を使用したマルチスレッド化の優れた入門書は C++ 11 Concurrency です。

1
knivil