当某些错误可以接受时,顺序加载存储原子的内存顺序应该是什么

时间:2022-04-16 18:06:22

Assume that a user is twisting a knob on a MIDI controller, and the values are being sent to my program as increments and decrements to a stored value. Twisting the knob one way will send a series of decrements, their value depending upon the speed of rotation; twisting the other way increments. I want to keep the stored value (and the value emitted by the following function) to between 0 and 100. If one or a few of the messages get dropped, that's no big deal, but I don't want unexpected major changes in the value emitted by the OffsetResult_ function.

假设用户正在扭动MIDI控制器上的旋钮,并且这些值将作为增量发送到我的程序并递减到存储值。单向旋转旋钮将发送一系列减量,其值取决于旋转速度;扭曲另一种方式增量。我想将存储的值(以及由以下函数发出的值)保持在0到100之间。如果一条或几条消息被删除,那没什么大不了的,但我不希望出现意外的重大变化。 OffsetResult_函数发出的值。

My question then is--do the following memory order directives look correct? The clearest one for me is the compare_exchange_strong. The program is using that as a store that can fail, so it seems that release memory ordering applies.

那么我的问题是 - 以下内存顺序指令看起来是否正确?对我来说最清楚的是compare_exchange_strong。该程序将其用作可能失败的存储,因此似乎适用于发布内存排序。

Can I even go to std::memory_order_relaxed since the major concern is just the atomicity of the changes to storedV, rather than remembering each change to storedV?

我是否可以转到std :: memory_order_relaxed,因为主要关注的是存储V的更改的原子性,而不是记住对storedV的每次更改?

Is there a general way to look at combined load/store functions to see whether it should be acquire, release, or sequentially consistent?

是否有一种通用方法来查看组合的加载/存储函数,以确定它应该是获取,释放还是顺序一致?

class ChannelModel {
    ChannelModel():currentV{0}{};
    int OffsetResult_(int diff) noexcept;
  private:
    atomic<int> storedV;
};

int ChannelModel::OffsetResult_(int diff) noexcept {
  int currentV = storedV.fetch_add(diff, std::memory_order_acquire) + diff;
  if (currentV < 0) {//fix storedV unless another thread has already altered it
    storedV.compare_exchange_strong(currentV, 0, std::memory_order_release, std::memory_order_relaxed);
    return 0;
  }
  if (currentV > 100) {//fix storedV unless another thread has already altered it
    storedV.compare_exchange_strong(currentV, 100, std::memory_order_release, std::memory_order_relaxed);
    return 100;
  }
  return currentV;
}

Note that the actual code is much more complex, and it is reasonable to believe that the response to each message from the controller will take long enough that this function will, on occasion, be called by two threads at nearly the same time.

请注意,实际代码要复杂得多,并且有理由相信来自控制器的每条消息的响应将花费足够长的时间,以便有时几乎同时由两个线程调用此函数。

1 个解决方案

#1


0  

I will make the assumption that currentV is a local variable in OffsetResult_. For some reason, it is initialized in the class constructor, but not defined as a class variable.

我将假设currentV是OffsetResult_中的局部变量。由于某种原因,它在类构造函数中初始化,但未定义为类变量。

You are changing the value of storedV with fetch_add and then adjust for possible errors with compare_exchange_strong. That is not correct... compare_exchange_strong is used here as a conditional store. Only if another thread does not changes the value, storedV will be updated.
The memory orderings you specify are incorrect.. In general, release ordering is used with an atomic store to indicate that data is 'released', ie. made available to another thread that will load from the same atomic using acquire ordering. release and acquire ordering form a run-time relationship and always come in pairs. That relationship is missing in your code when currentV is within its defined range since you never perform a release operation.

您正在使用fetch_add更改storedV的值,然后使用compare_exchange_strong调整可能的错误。这不正确... compare_exchange_strong在这里用作条件存储。仅当另一个线程未更改该值时,才会更新storedV。您指定的内存顺序不正确。通常,发布顺序与原子库一起使用以指示数据是“已释放”的,即。可用于另一个使用获取排序从同一原子加载的线程。从运行时关系中释放并获得订购,并且总是成对出现。由于您从未执行过释操作,因此当currentV在其定义的范围内时,代码中缺少该关系。

It is not clear why you want to specify orderings. Note that you don't have to set memory ordering in which case case the (safer) default (std::memory_order_seq_cst) will be used. Whether or not a weaker ordering is correct depends on the data it synchronizes between threads. With no data dependency, using std::memory_order_relaxed might be correct, but that context is missing in the code. However, since the atomic is associated with a knob value, it is likely that twisting the knob will result in some action that involves other data. I would not attempt to optimize with weaker memory orderings here. Chances are there will be no benefit at all since the Read-Modify-Write call (compare_exchange_x) is already relatively expensive. Also, if using a weaker memory ordering introduces a bug, it will be very hard to debug.

目前尚不清楚为什么要指定排序。请注意,您不必设置内存排序,在这种情况下将使用(更安全)默认值(std :: memory_order_seq_cst)。较弱的排序是否正确取决于它在线程之间同步的数据。在没有数据依赖性的情况下,使用std :: memory_order_relaxed可能是正确的,但代码中缺少该上下文。但是,由于原子与旋钮值相关联,因此旋转旋钮可能会导致某些涉及其他数据的动作。我不会尝试使用较弱的内存排序进行优化。由于Read-Modify-Write调用(compare_exchange_x)已经相对昂贵,因此根本没有任何好处。此外,如果使用较弱的内存排序引入了一个错误,那么调试将非常困难。

You can use std::compare_exchange_weak for the adjustment without losing updates:

您可以使用std :: compare_exchange_weak进行调整而不会丢失更新:

int ChannelModel::OffsetResult_(int diff) noexcept {
  int updatedV;

  int currentV = storedV.load();

  do {
    updatedV = currentV + diff;

    if (updatedV > 100)
        updatedV = 100;
    else if (updatedV < 0)
        updatedV = 0;

  } while (!storedV.compare_exchange_weak(currentV, updatedV));

  return updatedV;
}

The key is that compare_exchange_weak will only (atomically) update storedV if it is still (or again) equal to currentV. If that check fails, it will iterate through the loop again. Used in a loop, compare_exchange_weak (which may fail spuriously) is a better choice than compare_exchange_strong.

关键是compare_exchange_weak只会(原子地)更新storedV,如果它仍然(或再次)等于currentV。如果该检查失败,它将再次遍历循环。在循环中使用,compare_exchange_weak(可能是虚假失败)是比compare_exchange_strong更好的选择。

Memory ordering is a complex topic, here is a good overview.

内存排序是一个复杂的主题,这里是一个很好的概述。

#1


0  

I will make the assumption that currentV is a local variable in OffsetResult_. For some reason, it is initialized in the class constructor, but not defined as a class variable.

我将假设currentV是OffsetResult_中的局部变量。由于某种原因,它在类构造函数中初始化,但未定义为类变量。

You are changing the value of storedV with fetch_add and then adjust for possible errors with compare_exchange_strong. That is not correct... compare_exchange_strong is used here as a conditional store. Only if another thread does not changes the value, storedV will be updated.
The memory orderings you specify are incorrect.. In general, release ordering is used with an atomic store to indicate that data is 'released', ie. made available to another thread that will load from the same atomic using acquire ordering. release and acquire ordering form a run-time relationship and always come in pairs. That relationship is missing in your code when currentV is within its defined range since you never perform a release operation.

您正在使用fetch_add更改storedV的值,然后使用compare_exchange_strong调整可能的错误。这不正确... compare_exchange_strong在这里用作条件存储。仅当另一个线程未更改该值时,才会更新storedV。您指定的内存顺序不正确。通常,发布顺序与原子库一起使用以指示数据是“已释放”的,即。可用于另一个使用获取排序从同一原子加载的线程。从运行时关系中释放并获得订购,并且总是成对出现。由于您从未执行过释操作,因此当currentV在其定义的范围内时,代码中缺少该关系。

It is not clear why you want to specify orderings. Note that you don't have to set memory ordering in which case case the (safer) default (std::memory_order_seq_cst) will be used. Whether or not a weaker ordering is correct depends on the data it synchronizes between threads. With no data dependency, using std::memory_order_relaxed might be correct, but that context is missing in the code. However, since the atomic is associated with a knob value, it is likely that twisting the knob will result in some action that involves other data. I would not attempt to optimize with weaker memory orderings here. Chances are there will be no benefit at all since the Read-Modify-Write call (compare_exchange_x) is already relatively expensive. Also, if using a weaker memory ordering introduces a bug, it will be very hard to debug.

目前尚不清楚为什么要指定排序。请注意,您不必设置内存排序,在这种情况下将使用(更安全)默认值(std :: memory_order_seq_cst)。较弱的排序是否正确取决于它在线程之间同步的数据。在没有数据依赖性的情况下,使用std :: memory_order_relaxed可能是正确的,但代码中缺少该上下文。但是,由于原子与旋钮值相关联,因此旋转旋钮可能会导致某些涉及其他数据的动作。我不会尝试使用较弱的内存排序进行优化。由于Read-Modify-Write调用(compare_exchange_x)已经相对昂贵,因此根本没有任何好处。此外,如果使用较弱的内存排序引入了一个错误,那么调试将非常困难。

You can use std::compare_exchange_weak for the adjustment without losing updates:

您可以使用std :: compare_exchange_weak进行调整而不会丢失更新:

int ChannelModel::OffsetResult_(int diff) noexcept {
  int updatedV;

  int currentV = storedV.load();

  do {
    updatedV = currentV + diff;

    if (updatedV > 100)
        updatedV = 100;
    else if (updatedV < 0)
        updatedV = 0;

  } while (!storedV.compare_exchange_weak(currentV, updatedV));

  return updatedV;
}

The key is that compare_exchange_weak will only (atomically) update storedV if it is still (or again) equal to currentV. If that check fails, it will iterate through the loop again. Used in a loop, compare_exchange_weak (which may fail spuriously) is a better choice than compare_exchange_strong.

关键是compare_exchange_weak只会(原子地)更新storedV,如果它仍然(或再次)等于currentV。如果该检查失败,它将再次遍历循环。在循环中使用,compare_exchange_weak(可能是虚假失败)是比compare_exchange_strong更好的选择。

Memory ordering is a complex topic, here is a good overview.

内存排序是一个复杂的主题,这里是一个很好的概述。