简体   繁体   English

的std ::原子 <int> memory_order_relaxed多线程程序中的VS volatile sig_atomic_t

[英]std::atomic<int> memory_order_relaxed VS volatile sig_atomic_t in a multithreaded program

Does volatile sig_atomic_t give any memory order guarantees? volatile sig_atomic_t是否提供任何内存顺序保证? Eg if I need to just load/store an integer is it ok to use? 例如,如果我只需要加载/存储整数,是否可以使用?

Eg here: 例如:

volatile sig_atomic_t x = 0;
...
void f() {
  std::thread t([&] {x = 1;});
  while(x != 1) {/*waiting...*/}
  //done!
}

is it correct code? 这是正确的代码吗? Are there conditions it may not work? 有条件可能不起作用吗?

Note: This is a over-simplifed example, ie I am not looking for a better solution for the given piece of code. 注意:这是一个过于简化的示例,即我不想为给定的代码片段寻找更好的解决方案。 I just want to understand what kind of behaviour I could expect from volatile sig_atomic_t in a multithreaded program according to the C++ standard. 我只是想了解根据C ++标准,在多线程程序中我可以从volatile sig_atomic_t中得到什么样的行为。 Or, if it is a case, understand why behaviour is undefined. 或者,如果是这种情况,请理解为什么行为未定义。

I've found the following statement here : 我在这里发现了以下声明:

The library type sig_atomic_t does not provide inter-thread synchronization or memory ordering, only atomicity. 库类型sig_atomic_t不提供线程间同步或内存排序,仅提供原子性。

And if I compare it with this definition here : 如果我在这里将它与这个定义进行比较:

memory_order_relaxed: Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed memory_order_relaxed:放松操作:没有对其他读取或写入施加同步或排序约束,只保证此操作的原子性

Is it not the same? 是不一样的? What does exactly atomicity mean here? 究竟原子性在这里意味着什么? Does volatile do anything useful here? volatile在这里有用吗? What's difference between "does not provide synchronization or memory ordering" and "no synchronization or ordering constraints"? “不提供同步或内存排序”和“无同步或排序约束”之间有什么区别?

You are using an object of type sig_atomic_t that is accessed by two threads (with one modifying). 您正在使用sig_atomic_t类型的对象,该对象由两个线程访问(一个修改)。
Per the C++11 memory model, this is undefined behavior and the simple solution is to use std::atomic<T> 根据C ++ 11内存模型,这是未定义的行为,简单的解决方案是使用std::atomic<T>

std::sig_atomic_t and std::atomic<T> are in different leagues.. In portable code, one cannot be replaced by the other and vice versa. std::sig_atomic_tstd::atomic<T>在不同的联盟中。在便携式代码中,一个不能被另一个替换,反之亦然。

The only property that both share is atomicity (indivisible operations). 两者共享的唯一属性是原子性(不可分割的操作)。 That means that operations on objects of these types do not have an (observable) intermediate state, but that is as far as the similarities go. 这意味着对这些类型的对象的操作没有(可观察的)中间状态,但就相似性而言。

sig_atomic_t has no inter-thread properties. sig_atomic_t没有线程间属性。 In fact, if an object of this type is accessed (modified) by more than one thread (as in your example code), it is technically undefined behavior (data race); 实际上,如果这个类型的对象被多个线程访问(修改)(如示例代码中所示),那么它在技术上是未定义的行为(数据竞争); Therefore, inter-thread memory ordering properties are not defined. 因此,未定义线程间内存排序属性。

what is sig_atomic_t used for? 什么是sig_atomic_t用于?

An object of this type may be used in a signal handler, but only if it is declared volatile . 这种类型的对象可以在信号处理程序中使用,但前提是它被声明为volatile The atomicity and volatile guarantee 2 things: 原子性和volatile保证2件事:

  • atomicity: A signal handler can asynchronously store a value to the object and anyone reading the same variable (in the same thread) can only observe the before- or after value. 原子性:信号处理程序可以异步地将值存储到对象中,任何读取相同变量(在同一个线程中)的人只能观察到之前或之后的值。
  • volatile: A store cannot be 'optimized away' by the compiler and is therefore visible (in the same thread) at (or after) the point where the signal interrupted the execution. volatile:存储器不能被编译器“优化掉”,因此在信号中断执行的点(或之后)可见(在同一线程中)。

For example: 例如:

volatile sig_atomic_t quit {0};

void sig_handler(int signo)  // called upon arrival of a signal
{
    quit = 1;  // store value
}


void do_work()
{
    while (!quit)  // load value
    {
        ...
    }
}

Although this code is single-threaded, do_work can be interrupted asynchronously by a signal that triggers sig_handler and atomically changes the value of quit . 虽然这段代码是单线程的, do_work可以通过触发sig_handler并原子地改变quit值的信号异步中断。 Without volatile , the compiler may 'hoist' the load from quit out of the while loop, making it impossible for do_work to observe a change to quit caused by a signal. 如果没有volatile ,编译器可能会“ quit ”来自quit while循环的负载,使do_work无法观察到由信号引起的quit更改。

Why can't std::atomic<T> be used as a replacement for std::sig_atomic_t ? 为什么std::atomic<T>不能用作std::sig_atomic_t的替代?

Generally speaking, the std::atomic<T> template is a different type because it is designed to be accessed concurrently by multiple threads and provides inter-thread ordering guarantees. 一般来说, std::atomic<T>模板是一种不同的类型,因为它被设计为由多个线程并发访问,并提供线程间排序保证。 Atomicity is not always available at CPU level (especially for larger types T ) and therefore the implementation may use an internal lock to emulate atomic behavior. 原子性在CPU级别并不总是可用(特别是对于较大的类型T ),因此实现可能使用内部锁来模拟原子行为。 Whether std::atomic<T> uses a lock for a particular type T is available through member function is_lock_free() , or class constant is_always_lock_free (C++17). std::atomic<T>是否对特定类型使用锁定T可通过成员函数is_lock_free()或类常量is_always_lock_free (C ++ 17)获得。

The problem with using this type in a signal handler is that the C++ standard does not guarantee that a std::atomic<T> is lock-free for any type T . 在信号处理程序中使用此类型的问题是,C ++标准不保证std::atomic<T>对于任何类型T都是无锁的。 Only std::atomic_flag has that guarantee, but that is a different type. 只有std::atomic_flag才有这种保证,但这是一种不同的类型。

Imagine above code where the quit flag is a std::atomic<int> that happens to be not lock-free. 想象一下上面的代码,其中quit标志是std::atomic<int> ,恰好不是无锁的。 There is a chance that when do_work() loads the value, it is interrupted by a signal after acquiring the lock, but before releasing it. do_work()加载该值时,它有可能在获取锁之后但在释放之前被信号中断。 The signal triggers sig_handler() which now wants to store a value to quit by taking the same lock, which was already acquired by do_work , oops. 该信号触发sig_handler() ,它现在想通过获取do_work已经获得的同一个锁来存储一个值来quit This is undefined behavior and possibly causes a dead-lock. 这是未定义的行为,可能导致死锁。
std::sig_atomic_t does not have that problem because it does not use locking. std::sig_atomic_t没有这个问题,因为它没有使用锁定。 All that is needed is a type that is indivisible at CPU level and on many platforms, it can be as simple as: 所需要的只是在CPU级别和许多平台上不可分割的类型,它可以简单到:

typedef int sig_atomic_t;

The bottom line is, use volatile std::sig_atomic_t for signal handlers in a single thread and use std::atomic<T> as a data-race-free type, in a multi threaded environment. 底线是,在单线程中使用volatile std::sig_atomic_t作为信号处理程序,并在多线程环境中使用std::atomic<T>作为无数据竞争类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM