C ++同時実行ガイドstd :: atomic

std :: atomicの基本的な紹介

std :: atomicはテンプレートクラスです。テンプレートタイプTのアトミックオブジェクトは、タイプTの値をカプセル化します。

テンプレート構造体アトミック;

アトミックタイプオブジェクトの主な機能は、異なるスレッドからのアクセスがデータ競合を引き起こさないことです。したがって、異なるスレッドからアトミックオブジェクトにアクセスすることは明確に定義された動作であり、一般に非アトミックタイプの場合、オブジェクトへの同時アクセス(同期操作が実行されない場合)は未定義の動作になります。

std :: atomicコンストラクター

std :: atomicのコンストラクターは次のとおりです。
ここに画像の説明を挿入

  1. デフォルトのコンストラクターである、デフォルトのコンストラクターによって作成されたstd :: atomicオブジェクトは初期化されていない状態にあり、初期化されていない状態のstd :: atomicオブジェクトはatomic_init関数によって初期化できます。
  2. コンストラクターを初期化し、タイプTでstd :: atomicオブジェクトを初期化します。
  3. コピーコンストラクタは無効になっています。

次の例を考えてみましょう。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread>         // std::thread, std::this_thread::yield
#include <vector>         // std::vector
 
// 由 false 初始化一个 std::atomic<bool> 类型的原子变量
std::atomic<bool> ready(false);
std::atomic_flag winner = ATOMIC_FLAG_INIT;
 
void do_count1m(int id)
{
    
    
    while (!ready) {
    
     
        std::this_thread::yield();  // 等待 ready 变为 true.
    }
    
    for (volatile int i=0; i<1000000; ++i) {
    
    } // 计数
 
    if (!winner.test_and_set()) {
    
    
      std::cout << "thread #" << id << " won!\n";
    }
}
 
int main ()
{
    
    
    std::vector<std::thread> threads;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    
    for (int i=1; i<=10; ++i) 
        threads.push_back(std::thread(do_count1m,i));
        
    ready = true;
 
    for (auto& th : threads) 
        th.join();
        
    return 0;
}

std :: atomic :: operator =()関数

std :: atomicの割り当て操作機能は、次のように定義され
ここに画像の説明を挿入
ています。通常の割り当てコピー操作が無効になっていることがわかります。ただし、タイプTの変数は、対応するアトミックタイプ変数(暗黙的な変換と同等)に割り当てることができ、操作はアトミックであり、必要に応じて、メモリ順序(メモリ順序)はデフォルトで逐次一貫性(std :: memory_order_seq_cst)になります。指定他のメモリシーケンスには、std :: atomic :: store()を使用します。

#include <iostream>             // std::cout
#include <atomic>               // std::atomic
#include <thread>               // std::thread, std::this_thread::yield
 
std::atomic<int> foo(0);
 
void set_foo(int x)
{
    
    
    foo = x; // 调用 std::atomic::operator=().
}
 
void print_foo()
{
    
    
    while (foo == 0) {
    
     // wait while foo == 0
        std::this_thread::yield();
    }
    std::cout << "foo: " << foo << '\n';
}
 
int main()
{
    
    
    std::thread first(print_foo);
    std::thread second(set_foo, 10);
    
    first.join();
    second.join();
    
    return 0;
}

基本的なstd :: atomicタイプの操作

is_lock_free

bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;

std :: atomicオブジェクトにロックフリー特性があるかどうかを確認します。オブジェクトがロックフリー機能を満たしている場合、複数のスレッドがオブジェクトにアクセスしてもスレッドブロッキングは発生しません。(ロックフリー機能を実現するために、ある種のトランザクションメモリ方式を使用する場合があります)。

お店

void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
void store (T val, memory_order sync = memory_order_seq_cst) noexcept;

カプセル化された値を変更するために、std :: atomic :: store関数は、タイプTのパラメーターvalをアトミックオブジェクトによってカプセル化された値にコピーします。Tはstd :: atomicクラステンプレートパラメータです。さらに、パラメータsyncはメモリの順序を指定し、可能な値は次のとおりです。
ここに画像の説明を挿入
次の例を参照してください。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::thread
 
std::atomic<int> foo(0);  // 全局的原子对象 foo
 
void set_foo(int x)
{
    
    
    foo.store(x, std::memory_order_relaxed);     // 设置(store) 原子对象 foo 的值
}
 
void print_foo()
{
    
    
    int x;
    do {
    
    
        x = foo.load(std::memory_order_relaxed); // 读取(load) 原子对象 foo 的值
    } while (x == 0);
    
    std::cout << "foo: " << x << '\n';
}
 
int main ()
{
    
    
    std::thread first(print_foo);     // 线程 first 打印 foo 的值
    std::thread second(set_foo, 10);  // 线程 second 设置 foo 的值
    
    first.join();
    second.join();
    
    return 0;
}

負荷

T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
T load (memory_order sync = memory_order_seq_cst) const noexcept;

カプセル化された値を読み取り、パラメーターsyncはメモリ順序(メモリ順序)を設定します。可能な値は次のとおりです:
ここに画像の説明を挿入
上記のストアの例を参照してください。

演算子T

operator T() const volatile noexcept;
operator T() const noexcept;

load関数と同様に、カプセル化された値も読み取ります。演算子T()は型キャスト操作です。デフォルトのメモリ順序はstd :: memory_order_seq_cstです。他のメモリ順序を指定する必要がある場合は、load()関数を使用する必要があります。 。
次の例を考えてみましょう。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread, std::this_thread::yield
 
std::atomic<int> foo(0);
std::atomic<int> bar(0);
 
void set_foo(int x)
{
    
    
    foo = x;
}
 
void copy_foo_to_bar()
{
    
    
 
    // 如果 foo == 0,则该线程 yield,
    // 在 foo == 0 时, 实际也是隐含了类型转换操作,
    // 因此也包含了 operator T() const 的调用.
    while (foo == 0) 
        std::this_thread::yield();
 
    // 实际调用了 operator T() const, 将foo 强制转换成 int 类型,
    // 然后调用 operator=().
    bar = static_cast<int>(foo);
}
 
void print_bar()
{
    
    
    // 如果 bar == 0,则该线程 yield,
    // 在 bar == 0 时, 实际也是隐含了类型转换操作,
    // 因此也包含了 operator T() const 的调用.
    while (bar == 0) 
        std::this_thread::yield();
    std::cout << "bar: " << bar << '\n';
}
 
int main ()
{
    
    
    std::thread first(print_bar);
    std::thread second(set_foo, 10);
    std::thread third(copy_foo_to_bar);
 
    first.join();
    second.join();
    third.join();
    return 0;
}

両替

T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;

カプセル化された値を読み取って変更すると、exchangeはvalで指定された値を、以前にアトミックオブジェクトによってカプセル化された値に置き換え、以前にアトミックオブジェクトによってカプセル化された値を返します。プロセス全体がアトミックです(したがって、交換操作は読み取りとも呼ばれます)。 -変更-書き込み操作)。
syncパラメータはメモリ順序(メモリ順序)を指定し、可能な値は次のとおりです:
ここに画像の説明を挿入
以下の例を参照してください。各スレッドは1Mまでカウントされ、カウントタスクを完了するスレッドは最初に独自のIDを出力します。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
std::atomic<bool> ready(false);
std::atomic<bool> winner(false);
 
void count1m (int id)
{
    
    
    while (!ready) {
    
    }                      // wait for the ready signal
    for (int i = 0; i < 1000000; ++i) {
    
    }   // go!, count to 1 million
    
    if (!winner.exchange(true)) {
    
     
        std::cout << "thread #" << id << " won!\n"; 
    }
};
 
int main ()
{
    
    
    std::vector<std::thread> threads;
    std::cout << "spawning 10 threads that count to 1 million...\n";
    
    for (int i = 1; i <= 10; ++i) 
        threads.push_back(std::thread(count1m,i));
    
    ready = true;
    
    for (auto& th : threads) 
        th.join();
 
    return 0;
}

compare_exchange_weak

ここに画像の説明を挿入
次の場合、カプセル化された値(弱い)が期待されるパラメーターで指定された値と等しいかどうかを比較して交換します。

  • それらが等しい場合は、アトミックオブジェクトの古い値をvalに置き換えます。
  • 等しくない場合は、expectedをアトミックオブジェクトの古い値に置き換えます。したがって、この関数を呼び出した後、アトミックオブジェクトによってカプセル化された値がexpectedパラメータで指定された値と等しくない場合、expectedの内容はアトミックオブジェクトの古い値。

この関数は通常、アトミックオブジェクトによってカプセル化された値を読み取ります。比較が真の場合(つまり、アトミックオブジェクトの値が期待値と等しい場合)、アトミックオブジェクトの古い値が置き換えられますが、操作全体はアトミックです。スレッド内の値の読み取りと変更アトミックオブジェクトが使用されている場合、別のスレッドはアトミックオブジェクトを読み取ったり変更したりできません。

2番目のケースでは、メモリ順序の選択は、比較操作の結果によって異なります。比較の結果がtrueの場合(つまり、アトミックオブジェクトの値が期待値と等しい場合)、パラメータで指定されたメモリ順序成功が選択され、それ以外の場合はパラメータが選択されます。失敗指定されたメモリシーケンス。

この関数は、アトミックオブジェクトによってカプセル化された値を、期待されるパラメータの物理的な内容と直接比較することに注意してください。したがって、場合によっては、operator ==()で判断すると、オブジェクトの比較操作は同じになりますが、次の場合は失敗する可能性があります。オブジェクトのためにcompare_exchange_weakによって判断されます基礎となる物理コンテンツは、ビットアラインメントまたは同じであるが物理的に異なる値を表す他の論理表現を持っている可能性があります(たとえば、trueと2または3、どちらも論理的に「true」を表しますが、 2つの表現は、物理的には同じではありません)。

compare_exchange_strongとは異なり、compare-and-exchange操作の弱いバージョンでは、アトミックオブジェクトによってカプセル化された値を、期待されるパラメーターの物理コンテンツと同じにすることができますが、それでもfalseを返しますが、これはループを必要とする特定のアルゴリズムでは許容されます。また、一部のプラットフォームでは、compare_exchange_weakのパフォーマンスが向上しています。compare_exchange_weakの判断で誤った失敗が発生した場合、アトミックオブジェクトによってカプセル化された値が期待されるパラメーターの物理的な内容と同じであっても、判断操作の結果はfalseであり、compare_exchange_weak関数はfalseを返し、期待されるパラメータは変更されません。

ループ操作を使用する必要のない一部のアルゴリズムでは、通常、compare_exchange_strongを使用することをお勧めします。また、この関数のメモリ順序は、syncパラメータで指定されます。オプションの条件は次のとおりです。
ここに画像の説明を挿入
次の例を参照してください。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
// a simple global linked list:
struct Node {
    
     
    int value; 
    Node* next; 
};

std::atomic<Node*> list_head(nullptr);
 
void append(int val)
{
    
    
    // append an element to the list
    Node* newNode = new Node{
    
    val, list_head};
 
    // next is the same as: list_head = newNode, but in a thread-safe way:
    while (!list_head.compare_exchange_weak(newNode->next,newNode)) {
    
    }
    // (with newNode->next updated accordingly if some other thread just appended another node)
}
 
int main ()
{
    
    
    // spawn 10 threads to fill the linked list:
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) 
        threads.push_back(std::thread(append, i));
        
    for (auto& th : threads) 
        th.join();
 
    // print contents:
    for (Node* it = list_head; it!=nullptr; it=it->next)
        std::cout << ' ' << it->value;
 
    std::cout << '\n';
 
    // cleanup:
    Node* it; 
    while (it=list_head) {
    
    
        list_head=it->next; 
        delete it;
    }
 
    return 0;
}

compare_exchange_strong

ここに画像の説明を挿入
次の場合、カプセル化された値(strong)が、予期されるパラメーターで指定された値と等しいかどうかを比較して交換します。

  • それらが等しい場合は、アトミックオブジェクトの古い値をvalに置き換えます。
  • 等しくない場合は、expectedをアトミックオブジェクトの古い値に置き換えます。したがって、この関数を呼び出した後、アトミックオブジェクトによってカプセル化された値がexpectedパラメータで指定された値と等しくない場合、expectedの内容はアトミックオブジェクトの古い値。

この関数は通常、アトミックオブジェクトによってカプセル化された値を読み取ります。比較が真の場合(つまり、アトミックオブジェクトの値が期待値と等しい場合)、アトミックオブジェクトの古い値が置き換えられますが、操作全体はアトミックです。スレッド内の値の読み取りと変更アトミックオブジェクトが使用されている場合、別のスレッドはアトミックオブジェクトを読み取ったり変更したりできません。

2番目のケースでは、メモリ順序の選択は、比較操作の結果によって異なります。比較の結果がtrueの場合(つまり、アトミックオブジェクトの値が期待値と等しい場合)、パラメータで指定されたメモリ順序成功が選択され、それ以外の場合はパラメータが選択されます。失敗指定されたメモリシーケンス。

この関数は、アトミックオブジェクトによってカプセル化された値を、期待されるパラメータの物理的な内容と直接比較することに注意してください。したがって、場合によっては、operator ==()で判断すると、オブジェクトの比較操作は同じになりますが、次の場合は失敗する可能性があります。オブジェクトのためにcompare_exchange_weakによって判断されます基礎となる物理コンテンツは、ビットアラインメントまたは同じであるが物理的に異なる値を表す他の論理表現を持っている可能性があります(たとえば、trueと2または3、どちらも論理的に「true」を表しますが、 2つの表現は、物理的には同じではありません)。

compare_exchange_weakとは異なり、強力なバージョンの比較交換操作は(誤って)falseを返すことを許可されていません。つまり、アトミックオブジェクトによってカプセル化された値は、予期されるパラメーターの物理的な内容と同じであり、比較操作は真でなければなりません。ただし、一部のプラットフォームでは、アルゴリズム自体がチェックするためにループ操作を必要とする場合、compare_exchange_weakのパフォーマンスが向上します。

したがって、ループ操作を使用する必要のない一部のアルゴリズムでは、通常、compare_exchange_strongを使用することをお勧めします。また、この関数のメモリ順序は、syncパラメータで指定されます。オプションの条件は次のとおりです。
ここに画像の説明を挿入
次の例を参照してください。

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector
 
// a simple global linked list:
struct Node {
    
     
    int value; 
    Node* next; 
};
std::atomic<Node*> list_head(nullptr);
 
void append(int val)
{
    
    
    // append an element to the list
    Node* newNode = new Node{
    
    val, list_head};
 
    // next is the same as: list_head = newNode, but in a thread-safe way:
 
    while (!(list_head.compare_exchange_strong(newNode->next, newNode)));
    // (with newNode->next updated accordingly if some other thread just appended another node)
}
 
int main ()
{
    
    
    // spawn 10 threads to fill the linked list:
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) 
        threads.push_back(std::thread(append, i));

    for (auto& th : threads) 
        th.join();
 
    // print contents:
    for (Node* it = list_head; it!=nullptr; it=it->next)
        std::cout << ' ' << it->value;
 
    std::cout << '\n';
 
    // cleanup:
    Node* it; 
    while (it=list_head) {
    
    
        list_head=it->next; 
        delete it;
    }
 
    return 0;
}

おすすめ

転載: blog.csdn.net/qq_24649627/article/details/114366823