std :: asyncの使用の概要

C++98標準にはスレッドライブラリはありません。C++11中国がついに、スレッドの管理、共有データの保護、スレッド間の同期操作、およびアトミック操作のためのクラスを提供するマルチスレッド標準ライブラリを提供するようになりました。マルチスレッドライブラリに対応するヘッダーファイルは#include <thread>クラス名std::threadです。

ただし、スレッドは結局のところシステムに近いものであり、使用するのはまだあまり便利ではありません。特に、スレッドの同期とスレッドの実行結果の取得はより面倒です。単純thread.join()に結果を取得することはできません。結果を渡すためにスレッド共有変数を定義する必要があります。また、スレッド間の相互排除も考慮する必要があります。幸いなことにC++11std::asyncスレッドを作成しstd::futureて結果得ることができる比較的単純な非同期インターフェイス提供します以前は、スレッドをカプセル化して独自の非同期を実装していましたが、スレッド化されたクロスプラットフォームインターフェイスを使用できるようになったため、C ++マルチスレッドプログラミングが大幅に容易になりました。

std::async関数プロトタイプを最初に見てください

//(C++11 起) (C++17 前)
template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
    async( Function&& f, Args&&... args );

//(C++11 起) (C++17 前)
template< class Function, class... Args >
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
    async( std::launch policy, Function&& f, Args&&... args );  

最初のパラメータはスレッド作成戦略です。2つの戦略から選択できます

  • std::launch::async:asyncを呼び出すときにスレッドの作成を開始します。
  • std::launch::deferred:スレッドを作成するための遅延読み込みメソッド。asyncが呼び出されたときにスレッドは作成されず、getまたはwait offutureが呼び出されるまでスレッドは作成されません。

デフォルトの戦略は次のとおりです。std::launch::async | std::launch::deferredつまり、2つの戦略のコレクションです。これについては、後で詳しく説明します。

2番目のパラメーターはスレッド関数です

スレッド関数は受け入れ可能function, lambda expression, bind expression, or another function object

3番目のパラメーターはスレッド関数のパラメーターです

もう説明しない

戻り値std :: future

std::future非同期操作の結果にアクセスするメカニズムを提供するテンプレートクラスです。文字通り、それは未来を意味します。彼女はすぐには結果を取得しませんが、ある時点で同期して結果を取得できるため、非常に適切です。将来の状況を照会することで、非同期操作の構造を取得できます。future_statusには3つのステータスがあります。

  • 延期:非同期操作はまだ開始されていません
  • 準備完了:非同期操作が完了しました
  • タイムアウト:非同期操作のタイムアウト。主にstd :: future.wait_for()に使用されます。

例:

//查询future的状态
std::future_status status;
do {
    status = future.wait_for(std::chrono::seconds(1));
    if (status == std::future_status::deferred) {
        std::cout << "deferred" << std::endl;
    } else if (status == std::future_status::timeout) {
        std::cout << "timeout" << std::endl;
    } else if (status == std::future_status::ready) {
        std::cout << "ready!" << std::endl;
    }
} while (status != std::future_status::ready); 

std::future結果を得るには3つの方法があります。

  • get:非同期操作の終了を待ち、結果を返します
  • wait:非同期操作の終了を待っていますが、戻り値はありません
  • waite_for:結果を返すために待機しているタイムアウト、上記の例はタイムアウト待機の使用を示しています

std::async関数プロトタイプを導入し、どのように使用する必要がありますか?

std::async基本的な使用法:リンク例

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>
#include <string>
#include <mutex>

std::mutex m;
struct X {
    void foo(int i, const std::string& str) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << ' ' << i << '\n';
    }
    void bar(const std::string& str) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }
    int operator()(int i) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << i << '\n';
        return i + 10;
    }};

template <typename RandomIt>int parallel_sum(RandomIt beg, RandomIt end){
    auto len = end - beg;
    if (len < 1000)
        return std::accumulate(beg, end, 0);

    RandomIt mid = beg + len/2;
    auto handle = std::async(std::launch::async,
                             parallel_sum<RandomIt>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
}

int main(){
    std::vector<int> v(10000, 1);
    std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';

    X x;
    // 以默认策略调用 x.foo(42, "Hello") :
    // 可能同时打印 "Hello 42" 或延迟执行
    auto a1 = std::async(&X::foo, &x, 42, "Hello");
    // 以 deferred 策略调用 x.bar("world!")
    // 调用 a2.get() 或 a2.wait() 时打印 "world!"
    auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
    // 以 async 策略调用 X()(43) :
    // 同时打印 "43"
    auto a3 = std::async(std::launch::async, X(), 43);
    a2.wait();                     // 打印 "world!"
    std::cout << a3.get() << '\n'; // 打印 "53"
} // 若 a1 在此点未完成,则 a1 的析构函数在此打印 "Hello 42"

考えられる結果

The sum is 10000
43
world!
53
Hello 42

ことがわかるstd::async私たちは簡単にスレッドの作成の内部の詳細に注意を払うことなく、非同期実行状況や結果を得ることができるように非同期操作が、良いパッケージである、と我々はまた、スレッド作成戦略を指定することができます。

スレッド作成戦略の深い理解

  • std :: launch :: asyncスケジューリング戦略は、関数を非同期で実行する、つまり別のスレッドで実行する必要があることを意味します。
  • std :: launch :: deferredスケジューリング戦略は、std :: async呼び出しによって返される将来のオブジェクトがgetまたはwaitの場合にのみ関数を実行できることを意味します。つまり、呼び出しの1つが発生するまで、実行は延期されます。getまたはwaitを呼び出すと、関数は同期的に実行されます。つまり、呼び出し元は関数が終了するまでブロックします。getまたはwaitが呼び出されない場合、関数は実行されません。

どちらのストラテジーも非常に明確ですが、関数のデフォルトのストラテジーは非常に興味深いものです。明示的に指定したものではありません。つまり、最初の関数プロトタイプで使用されるストラテジー、つまりstd::launch::async | std::launch::deferredC ++標準で指定されている命令は次のとおりです。

非同期実行または遅延評価は、実装によって異なります

auto future = std::async(func);        // 使用默认发射模式执行func

このスケジューリング戦略では、関数funcがどのスレッドで実行されるか、または実行されるかどうかを予測する方法はありません。これは、funcが実行を延期するようにスケジュールされている可能性があるためです。つまり、getまたはwaitが呼び出されたときに実行されます。取得するか待機するかは、実行されるか、どのスレッドで実行されるかは予測できません。

同時に、このスケジューリング戦略の柔軟性により、thread_local変数の使用が混乱します。つまり、funcがこのスレッドローカルストレージ(スレッドローカルストレージ、TLS)を読み書きする場合、どのスレッドのローカル変数がフェッチされるかを予測することは不可能です。 。

また、待機ループに基づくタイムアウトにも影響します。これは、deferredwait_forまたはwait_untilを呼び出すと値std :: launch :: deferredが返されるというスケジューリング戦略である可能性があるためです。これは、最終的に停止するように見える次のループが実際には永久に実行される可能性があることを意味します。

void func()           // f睡眠1秒后返回
{
    std::this_thread::sleep_for(1);
}
auto future = std::async(func);      // (概念上)异步执行f
while(fut.wait_for(100ms) !=         // 循环直到f执行结束
      std::future_status::ready)     // 但这可能永远不会发生
{
    ...
}

無限ループに陥らないようにするには、futureがタスクを延期したかどうかを確認する必要がありますが、futureはタスクが延期されたかどうかを知ることができません。future_statusがwait_for(0)によって延期されているかどうかを取得することをお勧めします。

auto future = std::async(func);      // (概念上)异步执行f
if (fut.wait_for(0) == std::future_status::deferred)  // 如果任务被推迟
{
    ...     // fut使用get或wait来同步调用f
} else {            // 任务没有被推迟
    while(fut.wait_for(100ms) != std::future_status::ready) { // 不可能无限循环
      ...    // 任务没有被推迟也没有就绪,所以做一些并发的事情直到任务就绪
    }
    ...        // fut就绪
}

欠点がたくさんあるので、なぜそれを使うのかと言う人もいるかもしれません。結局、極端なケースの可能性を考慮しているので、同時にまたは同期して実行する必要がなく、thread_localの変更を検討する必要がないからです。同時に、可能なタスクが実行されないことを受け入れることもできます。この方法は、便利で効率的なスケジューリング戦略です。

要約すると、次の点を結論付けます。

  • std :: asyncのデフォルトのスケジューリング戦略により、タスクを非同期かつ同時に実行できます。
  • デフォルトの戦略の柔軟性は、thread_local変数を使用するときに不確実性をもたらします。これは、タスクが実行されない可能性があることを意味し、タイムアウトに基づく待機呼び出しのプログラムロジックにも影響します。
  • 非同期実行が必要な場合は、std :: launch :: async起動戦略を指定します。

参考記事:

APIリファレンスドキュメント

スレッド作成の代わりにC ++ 11 std :: asyncを使用する

おすすめ

転載: blog.csdn.net/weixin_41191739/article/details/113115847