スレッド同期アクション

11280 ワード

スレッド同期、すなわち、1つのスレッドが完了する前に、特定のことが発生するのを待つか、条件の達成を待つ必要があります.スレッド同期の動作は、条件変数(condition_variable)および所望の(future)で実現できる.
condition_variable
condition_variableヘッダファイルには、待機(wait)および通知(notify)シリーズに使用できる関数が含まれています.
待機関数
説明する
wait()
通知が起動するまでスレッドをブロック
wait_for()
タイムアウト待ちまたは通知による起動までスレッドをブロック
wait_until()
スレッドを一時的に停止したり、通知によって起動されたりします.
通知関数
説明する
notify_one()
現在待機している同じconditionを起動します.variableオブジェクトのスレッド
notify_all()
現在待機しているすべての同じconditionを起動します.variableオブジェクトのスレッド

#include            // std::cout
#include              // std::thread
#include               // std::mutex, std::unique_lock
#include  // std::condition_variable

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '
'
; } void go() { std::unique_lock<std::mutex> lck(mtx); ready = true; cv.notify_all(); } int main () { std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_id,i); std::cout << "10 threads ready to race...
"
; go(); // go! for (auto& th : threads) th.join(); return 0; }

コードでprint_id関数では,まずreadyに鍵をかけ,readyの値を判断する.readyがtrueでない場合、条件変数cvを使用してスレッドをブロックし、反発量をロックして他のスレッドにアクセスできるようにします.go()readyを変更した後、条件変数cvのnotify_を呼び出すall()は、cvブロックのすべてのスレッドに通知し、それらが起動され、信号量に対する競合状態に再進入し、反発ロックを獲得し、以下の動作を実行する.特筆すべきは、notify()シリーズ関数は同じcondition_に対してのみ使用されます.variableオブジェクトwait()のスレッドが通知操作を行う.次のようになります.
mutex m;
condition_variable cv1,cv2;
void foo(){
    ...
    lock_gound lck(m);
    cv1.wait(lck);
    ...
}
void bar(){
    ...
    lock_gound lck(m);
    cv2.wait(lck);
    ...
}
void cv_notify(){
    ...
    cv1.notify_one();
    ...
}
int main(){
    thread t1(foo);
    thread t2(bar);
    thread t3(cv_notify);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

上記コードではfoo()がcv 1でブロックされ、barがcv 2でブロックされ、cv_notify()は、cv 1によってブロックされたスレッドを起動します.ではfoo()は呼び覚まされ、bar()は別のcv 2の使用を待つしかない.notify_one()のスレッドが起動します.
future
futureヘッダファイルには、非同期操作を許可する一連の関数とクラスが含まれています.
async-非同期呼び出し関数
非同期呼び出し関数asyncはfnを呼び出し、futureオブジェクトがfnを持つ戻り結果を返し、戻り時にfnの実行完了を待たない.追加のパラメータも入力できます.このパラメータタイプはstd::launchで、fnがいつ呼び出されるかを制御します.
パラメータ
説明
launch::async
関数に独立したスレッドを作成し、すぐに実行します.
launch::deferred
関数の呼び出しがwait()またはget()関数呼び出しに遅延した場合に実行されます.
launch::deferred | launch::async
どちらでも構いません(デフォルト)
例:
#include
#include
using namespace std;
bool is_prime(int x){
    for(int i=2;iif(x%i==0){
            return false;
        }
    }
    return true;
}
int main(){
    future<bool> future1 = async(is_prime,21);    // ①
    ...
    bool ret =future1.get();    // ②
    if(ret){
        cout << "21 is prime";
    }else{
        cout << "21 is not prime";
    }
    return 0;
}

メイン関数では、1 async関数を呼び出してスレッド実行is_を作成します.prime()関数は、パラメータとして21を入力し、boolに特化したfutureオブジェクトを返します.その後、他の独自の操作を実行します.関数is_を取得する必要がある場合primeの戻り結果の場合、②はfuture 1のget()で戻り値を取得しretに与える.
packaged_task<>——タスク
packaged_taskクラスは関数をパッケージし、その結果を非同期でチェックし、get_を介してfutureオブジェクトに保存できます.future()を取得します.packaged_task<>のテンプレートパラメータは、fooパッケージのときにpackaged_を構築する関数署名を試みます.task<>オブジェクトはint foo(double, std::string&);に特化する必要があります.このように構成されたオブジェクトptはget_を呼び出すfuture()の場合に返されるfutureオブジェクトはpackaged_task pt(foo);型であるべきである.
packaged_taskで包装されたタスクは2つの方法で実行できます:1.packaged_の使用taskリロードoperator()パラメータ呼び出し2.threadオブジェクトを作成し、タスクとパラメータを呼び出します.
例:
#include
#include

using namespace std;

bool is_prime(int x) {

    for (int i = 2; i < x; i++) {
        if (x % i == 0) {
            return false;
        }
    }
    return true;
}

int main() {
    int x1 = 41, x2 = 42;
    //  operator()    pt1   
    packaged_task<bool(int)> pt1(is_prime);
    pt1(x1);
    auto ret1 = pt1.get_future().get();
    if (ret1) {
        cout << x1 << " is prime" << endl;
    } else {
        cout << x1 << " is not prime" << endl;
    }
    //          pt2   
    packaged_task<bool(int)> pt2(is_prime);
    future<bool> f = pt2.get_future();
    thread t(move(pt2), ref(x2));
    auto ret = f.get();
    if (ret) {
        cout << x2 << " is prime" << endl;
    } else {
        cout << x2 << " is not prime" << endl;
    }
    t.join();
    return 0;
}

メイン関数の最初のブロックコードはpackaged_を通過します.task<>のoperator()は、タスクパッケージの関数を呼び出し、このスレッドで実行します.複数のパラメータを受信し、パッケージされた関数に渡し、返されたfutureインスタンスを使用して関数の実行結果を取得できます.第2のブロックコードは、スレッドを作成することによってパッケージの関数を呼び出し、他のスレッドで実行し、futureによって結果を得る.
promise-承諾
Promiseオブジェクトは、あるタイプのTの値を保存することができます.この値はfutureオブジェクトで読み取ることができます(別のスレッドにおいても可能である)ため、promiseはスレッドを同期させる手段も提供する.get_futureにより、promiseオブジェクトに関連付けられたfutureオブジェクトを取得し、関数を呼び出した後、2つのオブジェクトは同じ共有状態を共有する.set_valueにより、関連付けられたpromiseオブジェクトとfutureオブジェクトとの間の共有値を設定することができる.promisの場合eオブジェクトが設定されていない場合はfutureを呼び出す.get()ではスレッドがブロックされます.set_value()の設定が完了し、対応するfutureがブロック状態から準備状態に変わり、格納された値を取得できます.例:
#include
#include
#include
using namespace std;

void print_future(future<int>& f){
    cout << "inside future"<cout << "get "<//     。             
}

int main() {
    promise<int> prom;    //  promise  
    future<int> fut = prom.get_future();    //        future
    thread t(print_future,ref(fut));        // future      
    this_thread::sleep_for(chrono::seconds(2));    //     ,  2 
    prom.set_value(20);                     //     
    t.join();
    return 0;
}