c++11コンカレントライブラリのスレッド同期

7659 ワード

  • 主な内容
  • 条件変数
  • future
  • async/packeged_task/promise
  • shared_future


  • じょうけんへんすう

    std::mutex _mutex;
    std::condition_variable _cv;
    std::deque<:string> _data;
    
    void thread_process_data()
    {
        while(1){
            std::unique_lock<:mutex> lk(_mutex);
            _cv.wait(lk, [](){return !_data.empty();});
            std::string str = _data.front();
            _data.pop();
            lk.unlock();
            do_something(str);
        }
    }
    
    void thread_produce_data()
    {
        while(1){
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
            {
                std::lock_guard<:mutex> lk(_mutex);
                _data.push("hello cv");
            }
            _cv.notify_one();
        }  
    }
    

    待機条件のスレッド処理プロセス:
  • は、共有変数
  • を保護するためのstd::unique_lock<:mutex/>を取得する.
  • wait(wait_forまたはwait_until)を実行し、これらの関数の呼び出しは、反発ロックを原子的に解放し、スレッドの実行を一時停止する.
  • 反発ロックを解放する前に、ここではlambdaの戻り値が初めてチェックされます.
  • 条件が真である場合、スレッドはwaitから返され、実行を継続する.
  • 条件が偽である場合、スレッドはブロックされ、同時に反発ロックを解放する.

  • 条件変数が通知され、タイムアウトが期限切れになったり、虚偽の起動が発生したりした場合、スレッドは起動され、反発ロックは原子的に再取得される.次に、起動が偽物である場合、スレッドは条件をチェックして待機し続ける必要があります.
  • 起動時に反発ロック
  • を再取得する.
  • lambda戻り値
  • を再確認
  • 条件が真であれば、スレッドは実行を継続する.
  • 条件が偽である場合、スレッドはブロックされ、同時に反発ロックを解放する.


  • std::unique_を使用するstdではなくlock::lock_guardの原因は2つあります.
    1、待機中のスレッドは、待機中に反発ロックを解除し、待機後に再びロックしなければならないが、std::lock_guardはこの柔軟性を提供しない.
    2、wait()の最初のパラメータはstd::unique_である.lockの参照.
    起動待ち条件のスレッド:
  • 取得std::lock_gurad<:mutex/>
  • 修正
  • を実行する.
  • 実行notify_oneまたはnotify_allの通知条件が発生すると、待機条件のスレッドが起動する(ロックを持つ必要がないことを通知する)
  • .
    condition variableは、あるイベントが繰り返されるシナリオに適用され、あるシナリオではスレッドが1回のイベントを真に待つだけで、その後、このイベントの発生を永遠に待つことはありません.

    future


    asyncとfuture
    asyncとthreadの違い:
  • std::threadはクラステンプレートであり、std::asyncは関数テンプレート
  • にすぎない
  • std::asyncはstd::futureオブジェクトを返し、非同期操作作成者に非同期結果にアクセスさせる.
  • std::threadは常に新しいスレッドを起動し、新しいスレッドでf
  • を直ちに実行する.
  • 呼び出しstd::asyncは必ずしも新しいスレッドを起動するとは限らず、f、遅延実行、
  • を直ちに実行するかどうかを決定することができる.
    asyncのpolicyパラメータ
  • はlaunch::asyncに設定され、f
  • は新しいスレッドで直ちに実行される.
  • はlaunch::deferredに設定され、asyncのfはfutureオブジェクトがクエリーされるまで実行を延期し(get/wait)、fはfutureオブジェクトをクエリーするスレッドで実行され、このスレッドは必ずしもasyncを呼び出すスレッドではない.futureオブジェクトがクエリーされていない場合、fは永遠に実行されません.
  • はlaunch::deferred|std::launch::asyncに設定されており、実装に関連して、直ちに非同期で実行するか、実行を遅らせる可能性があります.
  • launch::deferredとstd::launch::asyncは設定されていません.C++14では
  • が定義されていません.
    packaged_taskとfuture
  • クラステンプレートstd::packaged_taskは、Callable(関数、lambda、bind式、または他の関数オブジェクト)をパッケージします.
  • std::functionとは異なり、std::packaged_taskはstd::futureを提供した.
  • その威力はstd::futureにアクセスできる限りstd::packaged_にかかわらずtaskは、オブジェクトとしてどのスレッドに転送されて実行されるか、std::futureによってその結果を取得することができる.
  • インスタンス化std::packaged_task自体もCallableです.std::functionオブジェクトにパッケージ化したり、スレッド関数としてstd::threadに渡したり、Callableが必要な別の関数に渡したり、直接呼び出されたりすることができます.
  • std::packaged_taskは、スレッドプールまたはタスクキューの構築ブロックとして使用でき、スレッド間でメッセージとして渡されます.

  • パッケージlambda
    void task_lambda()
    {
        std::packaged_task task([](int a, int b) {
            return std::pow(a, b); 
        });
        std::future result = task.get_future();
        // std::pow(2,9)
        task(2, 9);
        std::cout << "task_lambda:\t" << result.get() << '
    '; }

    パッケージbind
    int f(int x, int y) { return std::pow(x,y); }
    void task_bind()
    {
        std::packaged_task task(bind(f,1,2));
        std::future result = task.get_future();
        // std::pow(2,9)
        task(2, 9);
        std::cout << "task_bind:\t" << result.get() << '
    '; }

    スレッド関数として
    int f(int x, int y) { return std::pow(x,y); }
    void task_thread()
    {
        std::packaged_task task(f);
        std::future result = task.get_future();
        // std::pow(2,9)
        std::thread t(std::move(task), 2, 9);
        std::cout << "task_thread:\t" << result.get() << '
    '; }

    同等:
    void equal_to_task_thread()
    {
        std::future result = std::async(std::launch::async, f, 2, 9);
        std::cout << "equal_to_async:\t" << result.get() << '
    '; }

    promiseとfuturestd::promiseは、後で関連するstd::futureオブジェクトを介して値を読み取ることができる値(タイプT)を設定する方法を提供する.ペアはfutureおよびpromiseを使用し、結果を待つスレッドはfuture::wait()またはfuture::get()でブロックされる可能性があり、データを提供するスレッドはペアのpromiseを使用して関連する値を設定し、futureを準備することができる.
    一般的なパターンは次のとおりです.
  • 結果タイプTを取得したいスレッド構築std::promise,promise::get_future取得futureオブジェクト
  • promiseをパラメータとして新しいスレッド関数に渡し、新しいスレッド関数でpromise::set_value設定結果
  • で待機しているスレッドはfuture::waitで結果が誕生し、future::getで結果が取得されます.
    void do_work(arg, std::promise promise_arg)
    {
        do_something();
        promise_arg.set_value(T);  
    }
      
    int main()
    {
        // promise 
        std::promise one_promise;
        // future 
        std::future one_future = one_promise.get_future();
        //promise 
        std::thread work_thread(do_work, some_arg, std::move(one_promise));
        // 
        one_future.wait();  
        // 
        std::cout << "result=" << one_future.get() << '
    '; work_thread.join(); }
  • promiseはコピーできません.oneを直接コピーすることはできません.promiseはパラメータとして新しいスレッド関数に渡される.

  • 異常とfuture
  • std::async、std::packeged_を使用する場合taskの場合、スレッド関数、パッケージされた関数が異常を投げ出すと、futureに関連付けられたデータには結果ではなく異常が格納されます.
  • std::promiseはちょっと違いますが、正常な結果ではなく異常を格納したい場合はset_を使用します.Exception()代替set_value.
    extern std::promise some_promise;
    
    try
    {
      some_promise.set_value(calculate_value());
    }
    catch(...)
    {
        //1 current_exception calculate_value() 
        //2 set_exception() 
      some_promise.set_exception(std::current_exception());
        // 
        //some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));
    }
    
  • 異常が格納されると、futureは準備が整い、future::get()を呼び出すことで格納された異常を再放出する.
  • 継続
  • std::shared_future
  • std::futureは、スレッド間でデータを転送するために必要なすべての同期を処理しますが、std::future特定のインスタンスのメンバー関数呼び出しは、スレッドが安全ではありません.複数のスレッドから単一のstd::futureオブジェクトにアクセスし、追加の同期を行わない場合、データ競合および未定義の動作に直面します.
    std::futureモデルは非同期結果に対して一意の所有権を持ち、1つのスレッドだけが非同期結果(getを介して)を抽出することができ、get()を最初に呼び出した後、抽出する値はもうありません.
  • std::shared_futureでは、複数のスレッドが同じイベントを待つことができます.
    std::futureはmoveableであり、非同期結果に対する一意の所有権はfutureインスタンス間で移行することができる(移動によって意味を構築する)が、毎回1つのインスタンスだけが特定の非同期結果を参照する.でもstd::shared_futureインスタンスはcopyableなので、shared_を複数作成できます.futureオブジェクトは同じ関連状態を参照する.
  • 単一shared_futureオブジェクトのメンバー関数はまだ同期していません.複数のスレッドから単一のオブジェクトにアクセスする場合は、データの競合を回避するためにロックを使用してアクセスを保護する必要があります.
    優先的な方法は、オブジェクトのコピーを取得し、各スレッドに独自のコピーにアクセスさせることです.各スレッドが独自のstd::shared_を通過する場合futureオブジェクトが共有非同期状態にアクセスすると、複数のスレッドからアクセスするのは安全です.
  • futureインスタンスによるshared_の構築futureインスタンスは、所有権を移転する方法で
    std::promise p;
    std::future f = p.get_future();
    
    // shared_future :
    std::shared_future sf(f);               //error
    std::shared_future sf(std::move(f));    //move 
    // :
    std::shared_future sf = f.share();
    // :
    std::shared_future sf(std::move(p.get_future()));
    
    assert(!f.valid());
    assert(sf.valid()); 
    
  • を行う必要があります.