std :: atomic< int>多线程程序中的memory_order_relaxed VS volatile sig_atomic_t [英] std::atomic<int> memory_order_relaxed VS volatile sig_atomic_t in a multithreaded program

查看:109
本文介绍了std :: atomic< int>多线程程序中的memory_order_relaxed VS volatile sig_atomic_t的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

volatile sig_atomic_t是否提供任何存储顺序保证?例如.如果我只需要加载/存储一个整数,就可以使用吗?

Does volatile sig_atomic_t give any memory order guarantees? E.g. if I need to just load/store an integer is it ok to use?

例如在这里:

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?

注意:这是一个过于简单的示例,即我不是在寻找给定代码段的更好解决方案.我只想了解在根据C ++标准的多线程程序中,从volatile sig_atomic_t可以期望什么样的行为.或者,如果是这种情况,请了解行为未定义的原因.

Note: This is a over-simplifed example, i.e. 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. Or, if it is a case, understand why behaviour is undefined.

我在此处找到了以下语句:

库类型sig_atomic_t不提供线程间同步或内存排序,仅提供原子性.

The library type sig_atomic_t does not provide inter-thread synchronization or memory ordering, only atomicity.

如果我将其与此定义进行比较,请此处:

And if I compare it with this definition here:

memory_order_relaxed:轻松的操作:对其他读取或写入没有同步或排序约束,仅保证此操作的原子性

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

不一样吗? atomicity 在这里到底是什么意思? volatile在这里有用吗? 不提供同步或内存排序"与不提供同步或内存排序约束"有什么区别?

Is it not the same? What does exactly atomicity mean here? Does volatile do anything useful here? What's difference between "does not provide synchronization or memory ordering" and "no synchronization or ordering constraints"?

推荐答案

您正在使用sig_atomic_t类型的对象,该对象可以由两个线程(进行一个修改)访问.
根据C ++ 11内存模型,这是未定义的行为,简单的解决方案是使用std::atomic<T>

You are using an object of type sig_atomic_t that is accessed by two threads (with one modifying).
Per the C++11 memory model, this is undefined behavior and the simple solution is to use std::atomic<T>

std::sig_atomic_tstd::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.

两者共享的唯一属性是原子性(不可分割的操作).这意味着对这些类型的对象进行的操作没有(可观察到的)中间状态,但是就相似程度而言.

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没有线程间属性.实际上,如果一个以上类型的对象被多个线程访问(修改)(如您的示例代码中所示),则从技术上讲它是未定义的行为(数据竞争); 因此,未定义线程间内存排序属性.

sig_atomic_t has no inter-thread properties. 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.

sig_atomic_t的用途是什么?

what is sig_atomic_t used for?

此类型的对象可以在信号处理程序中使用,但前提是必须声明为volatile.原子性和volatile保证两件事:

An object of this type may be used in a signal handler, but only if it is declared volatile. The atomicity and volatile guarantee 2 things:

  • 原子性:信号处理程序可以将值异步存储到对象,并且读取同一变量(在同一线程中)的任何人只能观察值的前后.
  • 易失性:存储区不能由编译器优化",因此在信号中断执行的位置(或之后)(在同一线程中)可见(在同一线程中).
  • 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 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
    {
        ...
    }
}

尽管此代码是单线程的,但do_work可以被触发sig_handler并自动更改quit值的信号异步中断. 如果没有volatile,编译器可能会将quit的负载释放"到while循环之外,从而使do_work无法观察到由信号引起的对quit的更改.

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. 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.

为什么不能使用std::atomic<T>代替std::sig_atomic_t?

Why can't std::atomic<T> be used as a replacement for std::sig_atomic_t?

通常来说,std::atomic<T>模板是另一种类型,因为它被设计为可以由多个线程并发访问,并提供线程间顺序保证. 原子性并不总是在CPU级别上可用(尤其是对于较大的类型T),因此实现可能会使用内部锁来模拟原子行为. 通过成员函数is_lock_free()或类常量is_always_lock_free(C ++ 17),可以确定std::atomic<T>是否对特定类型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. 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. 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).

在信号处理程序中使用此类型的问题是C ++标准不能保证std::atomic<T>对于任何类型T都是无锁的.只有std::atomic_flag具有该保证,但这是另一种类型.

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. Only std::atomic_flag has that guarantee, but that is a different type.

想象一下上面的代码,其中quit标志是一个std::atomic<int>碰巧不是非锁定的.当do_work()加载该值时, 在获得锁定之后但在释放它之前,该信号会被信号中断. 信号触发sig_handler(),现在希望通过采取相同的锁定来将值存储到quit,哎呀.这是未定义的行为,并可能导致死锁.
std::sig_atomic_t不存在此问题,因为它不使用锁定.所需要的只是一种在CPU级别和许多平台上都是不可分割的类型,它可以很简单:

Imagine above code where the quit flag is a std::atomic<int> that happens to be not lock-free. 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. 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. 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. All that is needed is a type that is indivisible at CPU level and on many platforms, it can be as simple as:

typedef int sig_atomic_t;

最重要的是,在多线程环境中,将volatile std::sig_atomic_t用作单线程中的信号处理程序,并将std::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.

这篇关于std :: atomic&lt; int&gt;多线程程序中的memory_order_relaxed VS volatile sig_atomic_t的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆