C++ atomic和memory_order

2019-09-26  本文已影响0人  土豆吞噬者

atomic

使用atomic可以保证数据读写的原子性,虽然mutex也能做到,但atomic的性能更好。atomic支持的类型有布尔类型,数值类型,指针类型,trivially_copyable类。

定义atomic时应该给一个初始值来初始化,或者调用atomic_init()来初始化。

atomic<bool> readyFlag(false);
atomic<bool> readyFlag;
atomic_init(&readyFlag, false);

使用store()写入值,load()读取值,这两个操作都是原子性的。

int main()
{
    atomic<bool> readyFlag(false);
    readyFlag.store(true);
    if (readyFlag.load()) {
        cout << "ok" << endl;
    }
    system("pause");
}

atomic类型也可以继续使用运算符,不过这些运算不是原子性的。

int main()
{
    atomic<bool> readyFlag(false);
    atomic<int> intValue(1);
    readyFlag = true;
    intValue = 100;
    intValue++;
    cout << intValue << endl;
    system("pause");
}

atomic的原子性可以用免锁的CPU原子指令来实现,也可以通过加锁的方式实现,使用is_lock_free()可以判断atomic对象是否免锁。

int main()
{
    atomic<bool> readyFlag(false);
    atomic<int> intValue(1);
    cout << intValue.is_lock_free() << endl;//1
    cout << readyFlag.is_lock_free() << endl;//1
    system("pause");
}

memory_order

处理器乱序执行编译器指令重排可能造成数据读写顺序错乱,CPU缓存可能造成数据更新不及时,memory_order的作用是明确数据的读写顺序以及数据的写入对其它线程的可见性。

下面代码中,(4)所运行的断言有可能失败,原因是我们不能保证(1)执行的修改一定会被B线程看到,(1)执行的修改也许会由于乱序在(2)的后面执行,就算在(2)的前面执行,CPU的读写缓存也有可能没有写回内存(每个CPU内核可能有各自独立的缓存)。

由于x86架构是acquire-release语义,所以下面的代码在x86 CPU上运行永远不会断言失败。

int data;
std::atomic_bool flag{ false };

// Execute in thread A
void producer() {
    data = 42;  // (1)
    flag.store(true);  // (2)
}

// Execute in thread B
void consume() {
    while (!flag.load());  // (3)
    assert(data == 42);  // (4)
}

C++标准库一共定义了6种memory_order,其中memory_order_acq_rel可以看作是memory_order_acquire和memory_order_release的合体:

typedef enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
    } memory_order;

内存顺序模型有下面四种,除非对性能要求很高,一般建议使用默认的序列一致顺序就行了,即使是序列一致顺序,性能也比mutex好。

下面代码中value = 100不允许被移动到readFlag.store(true, memory_order_release)后面执行,assert(value == 100)不允许移动到while (!readFlag.load(memory_order_acquire))前面执行

atomic<bool> readFlag(false);
int value = 0;

void provider()
{
    value = 100;
    readFlag.store(true, memory_order_release);
}

void consumer()
{
    while (!readFlag.load(memory_order_acquire));
    assert(value == 100);
}

int main()
{
    thread(provider).detach();
    thread(consumer).detach();
    system("pause");
}

atomic_flag

atomic_flag是一种简单的原子布尔类型,不能被拷贝,也不能 move 赋值,只支持两种操作,clear()设置值为false,test_and_set()设置值为true并返回之前的值。

一般使用ATOMIC_FLAG_INIT初始化atomic_flag使其处于 clear 状态 ,否则状态是未指定的。

使用atomic_flag实现的自旋锁:

atomic_flag g_lock = ATOMIC_FLAG_INIT;

void testFunc(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (g_lock.test_and_set(memory_order_acquire));  // acquire lock
        std::cout << "output from thread" << n << endl;
        g_lock.clear(memory_order_release);               // release lock
    }
}

int main()
{
    for (int i = 0; i < 5; ++i) {
        thread(testFunc, i).detach();
    }
    cin.get();
}
上一篇 下一篇

猜你喜欢

热点阅读