美文网首页
C++ atomic和memory_order

C++ atomic和memory_order

作者: 土豆吞噬者 | 来源:发表于2019-09-26 21:34 被阅读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好。

  • 宽松顺序(Relaxed ordering):原子操作带上memory_order_relaxed参数,仅保证操作是原子性的,不提供任何顺序约束。
  • 释放获得顺序(Release-Acquire ordering):对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_acquire调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的所有读写操作(B线程)不会在load()的前调用,A线程的所有写入操作对B线程可见。
  • 释放消费顺序(Release-Consume ordering):释放获得顺序的弱化版,对于同一个atomic,在线程A中使用memory_order_release调用store(),在线程B中使用memory_order_consume调用load()。这种模型保证在store()之前发生的所有读写操作(A线程)不会在store()后调用,在load()之后发生的依赖于该atomic的读写操作(B线程)不会在load()的前面调用,A线程对该atomic的带依赖写入操作对B线程可见。
  • 序列一致顺序(Sequential consistency):原子操作带上memory_order_seq_cst参数,这也是C++标准库的默认顺序,也是执行代价最大的,它是memory_order_acq_rel的加强版,如果是读取就是 acquire语义,如果是写入就是 release 语义,且全部读写操作顺序均一致。

下面代码中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();
}

相关文章

网友评论

      本文标题:C++ atomic和memory_order

      本文链接:https://www.haomeiwen.com/subject/ryzdyctx.html