haraduka's diary

やる気が欲しい

std::atomic使ったことなし。

std::atomicについて調べてみた…

  • 排他処理をする場合にlock_guardを使うとコストが高い。そこでstd::atomicを使う。
  • 複数のスレッドが同時に読み込んでもそれらの操作が順々に行われているように見えることを保証している、それがatomic変数
  • 最適化がマルチスレッドでの動作に悪影響を与えてしまうのを防ぐために最適化を防ぐ。それがメモリバリアという考え方。
  • メモリバリアには二種類あって、releaseバリア(先行する命令が後ろにリオーダーされるのを防ぐ)、aquireバリア(後述の命令がバリアを超えて前にリオーダーされるのを防ぐ)。- つまりstore_releaseとload_aquireがある。
  • これによって正しく同期化できる
  • サンプルコード
  • 競合が起こる例
#include <atomic>
#include <iostream>
#include <thread>

int val = 0;

int main()
{
    std::thread th1([&]() {for(int i=0; i<10000000; i++) val++; });
    std::thread th2([&]() {for(int i=0; i<10000000; i++) val--; });
    th1.join();
    th2.join();
    std::cout << val << std::endl;
}
  • std::atomicに最適化した書き方
#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int> val;

int main()
{
    std::thread th1([&]() {
            for(int i=0; i<10000000; i++) val++; //fetch_add(1)でもok
    });
    std::thread th2([&]() {
            for(int i=0; i<10000000; i++) val--; //fetch_sub(1)でもok
    });
    th1.join();
    th2.join();
    std::cout << val << std::endl;
}
  • spin-lockできるね!mutex…
#include <atomic>
#include <iostream>
#include <thread>

std::atomic<bool> bl(false);
int val = 0;
void spin_lock(void)
{
    //exchangeは引数を書き込んで、前の値を返す
    while (bl.exchange(true)) {  //exchange_aquire
    }
}

void spin_unlock(void)
{
    bl.store(false);  //store_release
}

int main()
{
    std::thread th1([&]() {
            for(int i=0; i<10000000; i++){
                spin_lock();
                val++;
                spin_unlock();
            }
    });
    std::thread th2([&]() {
            for(int i=0; i<10000000; i++){
                spin_lock();
                val--;
                spin_unlock();
            }
    });
    th1.join();
    th2.join();
    std::cout << val << std::endl;
}
  • 汎用的な書き方
#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int> val(0);

/* bool atomic<T>::compare_exchange_weak(T& expected, T desired)
 * 現在の値が expected に等しければ desired を書いて true を返す
 * そうでなければ expected を現在の値に書き換えて false を返す
 * これはexchangeが失敗する可能性があるため、loopが必要となる。
 */

/* bool atomic<T>::compare_exchange_strong(T& expected, T desired)
 * 現在の値が expected に等しければ desired を書いて true を返す
 * そうでなければ expected を現在の値に書き換えて false を返す
 * exchangeは必ず成功するためloopを使わない場合に使ってね。
 */

int main()
{
    std::thread th1([&]() {
            for(int i=0; i<10000000; i++){
                int expected = val.load();
                int desired;
                do {
                    desired = expected + 1;
                } while ( !val.compare_exchange_weak(expected, desired));
            }
    });
    std::thread th2([&]() {
            for (int i = 0; i < 10000000; i++) {
                int expected = val.load();
                int desired;
                do {
                    desired = expected - 1;
                } while (!val.compare_exchange_weak(expected, desired));
            }
    });
    th1.join();
    th2.join();
    std::cout << val << std::endl;
}