是什么使C ++原子成为原子

时间:2019-07-10 13:03:40

标签: c++ atomic

我只知道,即使代码int64_t_variable = 10也不是原子操作。例如

int64_t i = 0;
i = 100;

在另一个线程中(例如T2),它可能会读取不是0100的值。

  1. 以上是真的吗?
  2. std::atomic<int64_t> i; i.store(100, std::memory_order_relaxed)是原子的。那么基于Q1原子使用什么魔术来实现它是真的?
  3. 我总是认为处理少于64位的任何操作都是原子操作(假设64位cpu),看来我错了。因此对于v = n,我怎么知道它是否是原子的?例如,如果v为void *,是否为原子原子?

=================================

更新:我的问题是:当T2读取i时,0和100都适合我。但任何其他结果都不合理。这就是重点。因此,我认为CPU缓存或编译器之类的功能无法实现这一目标。

3 个答案:

答案 0 :(得分:4)

  

以上是真的吗?

是的。如果您不使用同步(std::atmoic<>std::mutex,...),那么您在一个线程中所做的任何更改都不会被假定显示在其他线程中。甚至可能是编译器优化了某些东西,因为它无法改变功能。例如

bool flag = true;

void stop_stuff() 
{ 
    flag = false; 
}

void do_stuff()
{
    while (flag)
        std::cout << "never stop";
}

因为flag没有同步,所以编译器可以自由地假设它永远不会改变,甚至可以在循环条件下检查标志。如果这样,无论您打stop_stuff多少次,do_stuff都将永远不会结束。

如果将标志更改为std::atomic<bool> flag,则编译器将无法做出这样的假设,因为您告诉它该变量可以在函数范围之外更改,因此需要对其进行检查。

请注意,当您有多个线程共享数据并且这些线程中至少有一个写入共享数据时,不提供同步称为数据竞争,按照标准,这是未定义的行为。上面的示例只是这种未定义行为的可能结果。

  

std::atomic<int64_t> i; i.store(100, std::memory_order_relaxed)是原子的。那么原子基于Q1使用什么魔术来实现呢?

它要么使用系统提供的原子基元,要么使用诸如std::mutex之类的锁定机制来保护访问。

  

我一直认为处理少于64位的任何操作都是原子操作(假设64位cpu),看来我错了。所以对于v = n,我怎么知道它是否是原子的?例如,如果v为空*,它是否是原子的?

尽管在某些系统上这可能是正确的,但对于C ++内存模型却并非如此。唯一原子的是std::atomic<T>

答案 1 :(得分:1)

  1. 是的。原因包括“它需要多个指令”,“内存缓存可能会使其混乱”和“如果您未提及该变量可能在其他位置更改,则编译器可能会做出令人惊讶的事情(aka UB)”。

  2. 上述所有原因都必须得到解决,std::atomic为编译器/ C ++库实现提供了这样做的信息。不允许编译器进行任何意外的优化,它可能会在必要时发出缓存刷新,并且实现可能使用锁定机制来防止对同一对象的不同操作发生交织。请注意,并非所有这些操作都是必需的:x86内置了一些原子性保证。您可以通知自己,给定的std::atomic类型是否在您的平台上始终使用std::atomic::is_always_lockfree是无锁的,以及是否给定的实例为lock-free(例如由于对齐/未对齐访问)。

  3. 如果您不使用std::atomic,则由于上述原因,您不能保证原子性。在CPU的指令级上,它可能是原子的,但是C ++是在没有此类保证的抽象机器上定义的。如果您的代码依赖抽象计算机上的原子性,但未能指定此原子性,则编译器可能会进行无效的优化并生成UB。

答案 2 :(得分:0)

  1. 是的,例如,如果有8位内存,则另一个线程可能会读取部分写入的变量。
  2. 无论为了保证正确的结果而采取什么措施,它都不可能是任何东西,都是原子操作,锁等内置的。
  3. void *不是原子std :: atomic 是。 对于char来说,也是相同的,要使其成为原子,必须将其明确声明为atomic。