首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

std::memory_order

Defined in header <atomic>

?

?

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

?

(since C++11)

std::memory_order指定如何围绕原子操作对常规的非原子内存访问进行排序。在多核系统上没有任何约束时,当多个线程同时读写多个变量时,一个线程可以观察到值的变化顺序与另一个线程所写的顺序不同。实际上,在多个读者线程之间,更改的明显顺序甚至可能有所不同。由于内存模型允许的编译器转换,即使在单处理器系统上也可能发生类似的影响。

库中所有原子操作的默认行为提供了顺序一致排序%28见%29下面的讨论。默认情况可能会影响性能,但是库%27s原子操作可以被赋予额外的std::memory_order参数来指定编译器和处理器必须为该操作强制执行的严格约束,而不是原子性。

常数

在标头中定义<atomic>

*。

价值解释

记忆[医]命令[医]放松轻松操作:对其他读写操作不施加同步或排序限制,只有此操作%27s原子性得到保证(28)--参见以下的松弛排序(%29)。

记忆[医]命令[医]使用此内存顺序的LOAD操作对受影响的内存位置执行消耗操作:在此加载之前,不能根据当前加载的值对当前线程中的任何读或写进行重新排序。写入释放相同原子变量的其他线程中的数据相关变量在当前线程中可见。在大多数平台上,这只会影响编译器优化--参见版本--使用低于%29的顺序。

记忆[医]命令[医]使用此内存顺序获取加载操作对受影响的内存位置执行获取操作:在此加载之前,不能对当前线程中的任何读或写进行重新排序。释放相同原子变量的其他线程中的所有写入在当前线程%28中都可见--请参阅版本-获取低于%29的顺序。

记忆[医]命令[医]使用此内存顺序进行的存储操作执行释放操作:在此存储之后,不能对当前线程中的任何读或写进行重新排序。当前线程中的所有写入在其他线程中都是可见的,这些线程获取相同的原子变量%28--获取低于%29的顺序,而携带依赖项的写入在其他使用相同原子%28-版本的线程中变得可见--消耗低于%29的顺序。

记忆[医]命令[医]acq[医]REL具有此内存顺序的读-修改-写入操作既是获取操作,也是释放操作.。在此存储之前或之后,不能重新排序当前线程中的内存读写。在修改之前,释放相同原子变量的其他线程中的所有写入都是可见的,而修改在获得相同原子变量的其他线程中是可见的。

记忆[医]命令[医]SEQ[医]带有此内存顺序的任何操作都是获取操作和释放操作,再加上一个总的顺序,其中所有线程都以相同的顺序观察所有修改(%28)---顺序一致地排序在%29以下。

形式描述

线程间同步和内存排序决定了评价副作用表达式在不同的执行线程之间排序。它们的定义如下:

排序-前

在同一线程中,求值A可能是排序-前评价B,如上文所述评价顺序...

携带依赖

在同一个线程中,求值A,即排序-前计算值B也可能将依赖项带到B%28中,也就是说,如果下列任何一项是正确的,则B依赖于A%29。

1%29 A的值用作B的操作数,

A%29如果B呼叫std::kill_dependency

B%29如果A是内置的左操作数&&,,,||,,,?:,或,操作员。

2%29 A写入标量对象M,B从M读取

3%29A将依赖关系带入另一评估X,X将依赖带入B

修改顺序

对任何特定原子变量的所有修改都是按照特定于这一个原子变量的总顺序进行的。

所有原子操作都有以下四项要求:

1%29写-写一致性如果评估A修改了一些原子M%28a,写%29发生-之前值B修改M,则A出现在修改顺序M

2%29读-读连贯如果某个原子M%28a的值计算A读%29发生-之前如果A的值来自于M上的写X,则B的值要么是X存储的值,要么是Y对M的副作用所存储的值,它出现在修改顺序M.

3%29读写一致性如果某个原子M%28a的值计算A读%29发生-之前对M%28a写%29的操作B,则A的值来自出现在修改顺序M

4%29写读一致性*如果副作用%28a在原子对象M上写入%29X发生-之前a值计算%28a读取M的%29B,则评估B应从X取其值,或从跟随X的副作用Y中取值,其修改顺序为M。

释放顺序

在...之后释放操作a是对一个原子对象M执行的,M是由M组成的修改顺序的最长连续子序列。

1%29次写入,由执行A的线程执行

2%29由任意线程对M进行的原子读-修改-写操作

被称为以A为首的释放序列...

依赖关系---按顺序排列

在线程之间,求值A是依赖关系---按顺序排列如果下列任何一项是正确的,则评估B。

1%29A表演释放操作在某些原子M上,在另一个线程中,B执行一个消耗操作在同一个原子M上,B读取由A领导的释放序列的任何部分所写的值。

2%29A是在X和X将依赖项带入B之前排序的依赖项。

线程间发生-之前

线程之间,计算A线程间发生在如果下列任何一项是正确的,则评估B。

1%29 A同步性

2%29 A依赖关系---按顺序排列

3%29 A同步性一些评价X,X是排序-前

3%29 A排序-前一些评价X和X线程间发生-之前

4%29 A线程间发生-之前一些评价X和X线程间发生-之前

发生-之前

不管线程,求值A发生-之前如果下列任何一项是正确的,则评价B:

1%29 A排序-前

2%29 A线程间发生在

实现是需要的,以确保发生-之前关系是无循环的,如果需要的话引入额外的同步(%28),只有在涉及消耗操作时才有必要,请参见Batty等人29%。

如果一个计算值修改了一个内存位置,而另一个值读取或修改了相同的内存位置,并且如果至少其中一个计算值不是原子操作,则程序的行为为未定义的%28,程序具有数据竞赛%29除非存在发生-之前这两项评价之间的关系。

可见副作用

标量M%28a写%29的副作用A是可见关于M%28a上的值计算B,如果以下两种情况都为真,请读%29:

1%29 A发生-之前

2%29没有其他副作用X对并购发生-之前X和X发生-之前

如果副作用A相对于值计算B是可见的,则副作用的最长连续子集在修改顺序,而B没有发生-之前它被称为可见的副作用序列.%28由B确定的M值将是这些副作用之一存储的值%29。

注意:线程间同步归结为通过建立关系%29之前建立数据竞赛%28,并定义在何种条件下哪些副作用变得可见。

消耗操作

原子负载memory_order_consume或者更强是一种消耗操作。请注意std::atomic_thread_fence与消耗操作相比,提出了更强的同步要求。

获取操作

原子负载memory_order_acquire或者更强大是一种获得的行动。对象的锁%28%29操作。Mutex也是一项收购行动。请注意std::atomic_thread_fence比获取操作更强的同步要求。

释放操作

原子存储memory_order_release或者更强的是释放操作。对象上的解锁%28%29操作。Mutex也是一个发布操作。请注意std::atomic_thread_fence比发布操作更强的同步要求。

解释

松弛排序

标记原子操作memory_order_relaxed不是同步操作;它们不会在并发内存访问之间强制执行顺序。它们只保证原子性和修改顺序的一致性。

例如,用xy一开始是零,

二次

代码语言:javascript
复制
// Thread 1:
r1 = y.load(memory_order_relaxed); // A
x.store(r1, memory_order_relaxed); // B
// Thread 2:
r2 = x.load(memory_order_relaxed); // C 
y.store(42, memory_order_relaxed); // D

二次

允许生产r1 == r2 == 42因为,虽然A是排序-前线程1和C中的B是前序在线程2中,没有什么能阻止D以y的修改顺序出现在A之前。在C之前出现的是x的修改顺序,D对y的副作用在线程1的负载A中可见,而B对x的副作用在线程2的负载C中可见。

Even with relaxed memory model, out-of-thin-air values are not allowed to circularly depend on their own computations, for example, with x and y initially zero, // Thread 1: r1 = x.load(memory_order_relaxed); if (r1 == 42) y.store(r1, memory_order_relaxed); // Thread 2: r2 = y.load(memory_order_relaxed); if (r2 == 42) x.store(42, memory_order_relaxed); is not allowed to produce r1 == r2 == 42 since the store of 42 to y is only possible if the store to x stores 42, which circularly depends on the store to y storing 42. Note that until C++14, this was technically allowed by the specification, but not recommended for implementors.

(since C++14)

放松内存排序的典型用途是递增计数器,例如std::shared_ptr,因为这只需要原子性,而不需要排序或同步%28--注意,减少共享[医]PTR计数器需要与析构函数%29进行获取-释放同步.

二次

代码语言:javascript
复制
#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
 
std::atomic<int> cnt = {0};
 
void f()
{
    for (int n = 0; n < 1000; ++n) {
        cnt.fetch_add(1, std::memory_order_relaxed);
    }
}
 
int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f);
    }
    for (auto& t : v) {
        t.join();
    }
    std::cout << "Final counter value is " << cnt << '\n';
}

二次

产出:

二次

代码语言:javascript
复制
Final counter value is 10000

二次

释放-获取命令

如果线程A中的原子存储被标记memory_order_release并且标记来自同一个变量的线程B中的原子负载。memory_order_acquire,所有内存都写入%28非原子和松弛原子%29,即发生过-之前从线程A的角度来看,原子存储成为可见副作用在线程B中,即一旦完成原子加载,线程B就保证看到线程A写入内存的所有内容。

同步只在线程之间建立。释放获取同样的原子变量。其他线程可以看到不同的内存访问顺序,而不是任何一个或两个同步线程。

在强排序系统%28x86、SPARC TSO、IBM大型机%29上,对大多数操作来说,发布-获取排序是自动的。对于此同步模式不发出额外的CPU指令,只有某些编译器优化会受到%28例如的影响。编译器被禁止移动非原子存储在原子存储释放或执行非原子负载之前-获取%29。在弱排序系统%28 ARM,Itanium,PowerPC%29,特殊CPU负载或内存栅栏指令必须使用。

互斥锁%28,如std::mutex或原子自旋锁%29是一个释放-获取同步的例子:当锁由线程A释放,由线程B获取时。在线程A上下文中的发布%29之前,关键部分%28中发生的所有事情都必须在获取%29之后对线程B%28可见,后者正在执行相同的关键部分。

二次

代码语言:javascript
复制
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
 
std::atomic<std::string*> ptr;
int data;
 
void producer()
{
    std::string* p  = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}
 
void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fires
    assert(data == 42); // never fires
}
 
int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

二次

下面的示例演示跨三个线程的传递发布-获取排序。

二次

代码语言:javascript
复制
#include <thread>
#include <atomic>
#include <cassert>
#include <vector>
 
std::vector<int> data;
std::atomic<int> flag = {0};
 
void thread_1()
{
    data.push_back(42);
    flag.store(1, std::memory_order_release);
}
 
void thread_2()
{
    int expected=1;
    while (!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) {
        expected = 1;
    }
}
 
void thread_3()
{
    while (flag.load(std::memory_order_acquire) < 2)
        ;
    assert(data.at(0) == 42); // will never fire
}
 
int main()
{
    std::thread a(thread_1);
    std::thread b(thread_2);
    std::thread c(thread_3);
    a.join(); b.join(); c.join();
}

二次

释放-消耗顺序

如果线程A中的原子存储被标记memory_order_release并且标记来自同一个变量的线程B中的原子负载。memory_order_consume,所有内存都写入%28非原子和松弛原子%29,这些依赖-有序-之前从线程A的角度来看,原子存储成为可见副作用在线程B中的这些操作中,其中加载操作进入线程B。携带依赖也就是说,一旦完成原子加载,线程B中使用从加载中获得的值的操作符和函数就可以确保查看线程A写入内存的内容。

同步只在线程之间建立。释放消费同样的原子变量。其他线程可以看到不同的内存访问顺序,而不是任何一个或两个同步线程。

在除DEC Alpha之外的所有主流CPU上,依赖排序是自动的,这种同步模式不需要额外的CPU指令,只有某些编译器优化会受到影响。编译器被禁止对依赖链%29中涉及的对象执行推测负载。

这种排序的典型用例包括对很少写入的并发数据结构的读取访问(%28路由表、配置、安全策略、防火墙规则等),以及带有指针介导的发布的发行者-订阅者情况,即。当生产者发布一个指针,使用者可以通过指针访问信息:没有必要让生产者写到内存的所有东西对使用者%28可见,这可能是弱有序体系结构%29上的昂贵操作。这种情况的一个例子是RCU[医]脱节...

另见std::kill_dependency和[[载运[医][受抚养人]]用于细粒度依赖链控制。

注意,目前%282/2015%29没有已知的生产编译器跟踪依赖链:取消消费操作以获取操作。

The specification of release-consume ordering is being revised, and the use of memory_order_consume is temporarily discouraged.

(since C++17)

此示例演示指针介导的发布的依赖顺序同步:整数数据与数据依赖关系中的字符串指针无关,因此其值在使用者中未定义。

二次

代码语言:javascript
复制
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
 
std::atomic<std::string*> ptr;
int data;
 
void producer()
{
    std::string* p  = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}
 
void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_consume)))
        ;
    assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr
    assert(data == 42); // may or may not fire: data does not carry dependency from ptr
}
 
int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

二次

顺序一致排序

标记原子操作memory_order_seq_cst不仅以与发布/获取排序%28相同的方式订购内存,发生过-之前一个线程中的存储变成一个可见副作用在执行加载%29的线程中,但也建立了一个单总修改顺序所有被标记的原子操作。

正式的,

memory_order_seq_cst从原子变量M加载的操作B注意到以下情况之一:

  • 最后一次操作A的结果,该操作修改了M,该操作出现在B之前,且以单一的全序出现。
  • 或者,如果有这样的A,B可能会观察到对M的一些修改的结果,而这不是memory_order_seq_cst也没有发生-之前
  • 或者,如果有%27T这样的A,B可以观察到一些无关的修改的结果,而M不是。memory_order_seq_cst

如果有一个memory_order_seq_cststd::atomic_thread_fence行动X排序-前B,则B注意到以下一项:

  • 最后一次memory_order_seq_cst对X前面出现的M的一次全序修改
  • 在M%27s修改顺序中出现的一些不相关的M修改

对于称为A和B的一对原子运算,其中A写和B读取M%27s值,如果有两个memory_order_seq_cststd::atomic_thread_fenceSX和Y,如果A是排序-前X,Y是排序-前B和X出现在Y之前的单一的总计顺序,然后B观察到:

  • A的作用
  • M%27S修改顺序A后出现的一些无关的M修饰

对于一对名为A和B的原子修饰,B发生在A之后,以M%27s的修改顺序进行,如果。

  • 有一个memory_order_seq_cststd::atomic_thread_fenceX使得A是排序-前X和X以单一的总顺序出现在B前面
  • 或者,有一个memory_order_seq_cststd::atomic_thread_fenceY是这样的排序-前B和A出现在Y前面,按单一的总顺序排列
  • 或者,有memory_order_seq_cststd::atomic_thread_fenceSX和Y使得A是排序-前X,Y是排序-前B和X出现在Y前面,按单一的总计顺序排列。

请注意,这意味着:

1%29只要原子操作没有标记memory_order_seq_cst输入图片,顺序一致性将丢失。

2%29顺序一致的篱笆只是为栅栏本身建立了全序,而在一般情况下,原子运算没有建立全序。排序-前不是一种跨线程关系,不像发生-之前%29

顺序订购对于多个生产者-多个消费者情况可能是必要的,在这种情况下,所有消费者都必须观察所有生产者在同一顺序下的行为。

全顺序排序要求在所有多核系统上都有一个完整的内存隔离CPU指令。这可能成为性能瓶颈,因为它迫使受影响的内存访问传播到每个核心。

此示例演示了需要顺序排序的情况。任何其他排序都可能触发断言,因为线程可以使用断言。cd观察原子的变化xy顺序相反。

二次

代码语言:javascript
复制
#include <thread>
#include <atomic>
#include <cassert>
 
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
 
void write_x()
{
    x.store(true, std::memory_order_seq_cst);
}
 
void write_y()
{
    y.store(true, std::memory_order_seq_cst);
}
 
void read_x_then_y()
{
    while (!x.load(std::memory_order_seq_cst))
        ;
    if (y.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
void read_y_then_x()
{
    while (!y.load(std::memory_order_seq_cst))
        ;
    if (x.load(std::memory_order_seq_cst)) {
        ++z;
    }
}
 
int main()
{
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join(); b.join(); c.join(); d.join();
    assert(z.load() != 0);  // will never happen
}

二次

与...的关系volatile

在执行线程中,访问%28读写%29挥发性极大值无法重新排序过去可观察到的副作用%28,包括其他易失性访问%29排序-前序后在同一线程中,但该顺序不能由另一个线程来保证,因为易失性访问不建立线程间同步。

此外,易失性访问不是原子%28并发读和写是一个数据竞赛%29和不顺序存储器%28非易失性存储器访问可以在易失性访问%29周围自由地重新排序。

一个值得注意的例外是VisualStudio,在默认设置中,每个易失性写入都具有发布语义,而每个易失性读都获得语义%28MSDN因此,挥发物可用于线程间同步。标准volatile语义不适用于多线程编程,尽管它们足以用于例如与std::signal处理程序,该处理程序在应用于sig_atomic_t变量。

另见

C内存顺序文档

*。

外部链接

代码语言:txt
复制
 ? cppreference.com

在CreativeCommonsAttribution下授权-ShareAlike未移植许可v3.0。

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com