关于c ++:std :: atomic究竟是什么?

What exactly is std::atomic?

我知道std::atomic<>是一个原子物体。但是原子能达到什么程度?据我所知,操作可以是原子的。使物体成为原子的确切含义是什么?例如,如果有两个线程同时执行以下代码:

1
a = a + 12;

那么整个操作(比如add_twelve_to(int)是原子的吗?还是对可变原子进行了更改(所以operator=()


std::atomic<>的每个实例化和完全专用化表示一种类型,不同的线程可以同时操作(它们的实例),而不会引发未定义的行为:

Objects of atomic types are the only C++ objects that are free from data races; that is, if one thread writes to an atomic object while another thread reads from it, the behavior is well-defined.

In addition, accesses to atomic objects may establish inter-thread synchronization and order non-atomic memory accesses as specified by std::memory_order.

std::atomic<>包装操作,在C++之前的11次操作中,必须使用(例如)与msvc或原子bultin的互锁函数(以防GCC)来执行。

另外,std::atomic<>通过允许指定同步和排序约束的各种内存顺序,为您提供了更多的控制。如果您想阅读更多关于C++ 11原子和内存模型的内容,这些链接可能是有用的:

  • C++原子与内存排序
  • 比较:在C++ 11中使用原子锁与互斥锁和RW锁的无锁编程
  • C++ 11引入了标准化内存模型。这是什么意思?它会如何影响C++程序设计?
  • C++ 11中的并发性

注意,对于典型的用例,您可能会使用重载的算术运算符或它们的另一组:

1
2
3
std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

因为运算符语法不允许您指定内存顺序,所以这些操作将用EDCOX1 OR 8来执行,因为这是C++ 11中所有原子操作的默认顺序。它保证所有原子操作之间的顺序一致性(全局排序)。

然而,在某些情况下,这可能不是必需的(没有免费的东西),因此您可能希望使用更明确的形式:

1
2
3
std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

现在,您的示例:

1
a = a + 12;

不会对单个原子操作进行评估:它将导致a.load()(原子本身),然后在该值与最终结果的12a.store()之间相加(也是原子)。如前所述,这里将使用std::memory_order_seq_cst

但是,如果你写a += 12,它将是一个原子操作(如我之前提到的),大致相当于a.fetch_add(12, std::memory_order_seq_cst)

关于你的评论:

A regular int has atomic loads and stores. Whats the point of wrapping it with atomic<>?

您的声明只适用于为存储和/或加载提供原子性保证的体系结构。有一些体系结构不能做到这一点。此外,通常要求必须在字/双字对齐地址上执行操作,使其成为原子std::atomic<>是一种保证在每个平台上都是原子的操作,而无需附加要求。此外,它允许您编写这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

注意,断言条件将始终为真(因此永远不会触发),因此您可以始终确保,在while循环退出后数据已就绪。这是因为:

  • store()to the flag在sharedData设置后执行(我们假设generateData()总是返回有用的东西,特别是从不返回NULL并使用std::memory_order_release顺序:

memory_order_release

A store operation with this memory order performs the release
operation: no reads or writes in the current thread can be reordered
after this store. All writes in the current thread are visible in
other threads that acquire the same atomic variable

  • sharedDatawhile循环退出后使用,因此在load()from标志返回非零值后使用。load()使用std::memory_order_acquire命令:

std::memory_order_acquire

A load operation with this memory order performs the acquire operation
on the affected memory location: no reads or writes in the current
thread can be reordered before this load. All writes in other threads
that release the same atomic variable are visible in the current
thread.

这使您能够精确地控制同步,并允许您显式地指定代码可能/可能不会/将/不会的行为。如果只保证原子性本身,这是不可能的。尤其是当涉及到非常有趣的同步模型时,比如发行版消耗顺序。


I understand that std::atomic<> makes an object atomic.

这是个观点问题…您不能将它应用于任意对象并使其操作成为原子的,但是可以使用为(大多数)整型和指针提供的特殊化。

a = a + 12;

std::atomic<>不(使用模板表达式)将其简化为单个原子操作,而operator T() const volatile noexcept成员执行a的原子load(),然后添加12个,operator=(T t) noexcept执行store(t)