C++ atomic和memory_order

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();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • C++ 11 atomic 简介 Atomic类型是c++11里面引入的一种类型,它规定了当程序的多个线程同时访问...
    EFlql阅读 7,726评论 1 1
  • 程序世界的barrier 同步屏障(Barrier)是并行计算中的一种同步方法。对于一群进程或线程,程序中的一个同...
    AKTeamYang阅读 4,675评论 0 2
  • 参考cppreference参考The C++ Memory Model and Modern Hardware ...
    王侦阅读 3,150评论 0 3
  • 不讲语言特性,只从工程角度出发,个人觉得C++标准委员会在C++11中对多线程库的引入是有史以来做得最人道的一件事...
    stidio阅读 13,259评论 0 11
  • atomic types是可以封装值的类型,保证不产生数据竞争,可用于多线程间内存访问的同步。 1.类classe...
    王侦阅读 4,053评论 0 0