スポンサーリンク

condition_variableの使用方法勉強中(1)

スレッドを待機させるためのcondition_variableの使い方を確認中。

とりあえず使い方や注意点をまとめたコードを書いてみたがなんか何をやりたいのかよくわからないコードになったのでもう少し考える。

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <random>
#include <chrono>

std::vector<double> data;
std::mutex mtx;
std::condition_variable cv;

// データが10個以上あればメインスレッドを起こすという条件を定義
// 判断中にdata.size()が変化する可能性があるので、必ずロック中に呼び出すこと
bool allow_to_main_wakeup() {
    return data.size() >= 10;
}

// 無限にデータを生成し続ける関数
// ただし、10個データが貯まったらメインスレッドで処理する要求を出す
// メインスレッドがデータを移動する間、このスレッドは休止する
void Factory() {

    // 値を生成する関数
    int count = 0;
    auto create_value = [&count]() { return count++; };

    // 無限にデータを生成してdataに追加し続ける
    while (true) {

        bool allow_to_wakeup = false;
        {
            // 仮に、先にメインスレッドでlockした場合、このスレッドはlock(mtx); でブロック中となる
            // メインスレッド側ですぐにcv.wait()で待機開始するため、すぐにこのブロックは解除されることが期待できる
            std::unique_lock<std::mutex> lock(mtx);

            //==================================
            // データ作成処理本体
            data.push_back( create_value() );
            //==================================

            // メインスレッドで処理してよいかを判断。
            // data.size()が別スレッドで変化しないために、ロック中に判断する
            allow_to_wakeup = allow_to_main_wakeup(); 


            // 仮に、先にFactoryでlockした場合、メインスレッドはlock(mtx); でブロック中となっている
            // ここでスコープを抜けてunlockすることでメインスレッドがlockできるようになり、すぐにcv.wait()で待機開始
        }

        // 現在メインスレッドはcv.wait()で待機中となっている。
        // 特定の条件を満たしたら、メインスレッドで処理するためにメインスレッドを起こす処理をする
        if (allow_to_wakeup == true) {

            // notify_oneは同じcv.waitにより待機中のスレッドを一つだけ起こす。
            cv.notify_one();

            // notify_one()はこのスレッドには何もしないのでここはすぐに処理される
            // これ以降の処理はmainスレッドとの並列処理となる

        }

        // あまり速く動いても動作確認がしづらいのでスリープを入れる
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {

    // Factory関数を別スレッドで実行
    // 以後、別スレッドでデータが自動生成され続ける
    std::thread producer(Factory);

    std::vector<double> tmp;

    // 
    while (true) {

        {
            // ここでロック。ただしFactory関数側とどちらが先にロックするかは不定
            std::unique_lock<std::mutex> lock(mtx);

            // waitすると内部でmtx.unlock()が呼ばれる
            // これによりFactory側でlock(mtx)ができるようになり、データの生成が可能になる
            // 同時に、このスレッドはFactory側でnotify_one()が呼ばれるまでここで待機
            cv.wait(lock, [] { return allow_to_main_wakeup(); });
            // cv.waitのラムダ式はSpurious Wakeup対策。
            // 希にwait()中に何もしていないのにスレッドが起こされることがある
            // そのため、起こされたときに本当に起こされるべきかを関数により確認する        

            // ここから先はcv.notify_one()により起こされてから実行される
            // これ以降の処理はFactory関数との並列処理となる。
            // なおwait()を抜けた時点で、lockは再びlock状態となる

            //==================================
            // 別スレッドで作成されたデータを移動
            tmp = std::move(data);
            //==================================
        }

        std::cout << "Generated numbers: ";
        for (double num : tmp) {
            std::cout << num << " ";
        }
        std::cout << std::endl;
    }



    producer.join();
    return 0;
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: