假设有两个函数可以更新和返回被测属性的平均值:
void Class::Update( int delta )
{
m_accumulatedValue += delta;
++ m_count;
}
double Class::GetAverage( )
{
return m_accumulatedValue/(double)m_count;
}
现在,假设需要将它们更改为在具有线程池的多线程环境中运行,线程池中可以请求任何线程执行其中一个 - 也就是说,执行它们中的每一个的线程可以是不同的线程时间:
std::atomic< int > m_accumulatedValue;
std::atomic< int > m_count;
// ...
void Class::Update( int delta )
{
m_accumulatedValue.fetch_add( delta , std::memory_order_relaxed );
m_count.fetch_add( 1 , std::memory_order_release );
}
double Class::GetAverage( )
{
auto count = m_count.load( std::memory_order_acquire );
auto acc = m_accumulatedValue.load( std::memory_order_relaxed );
return acc/(double)count;
}
我正在尝试理解获取和释放内存的顺序。
假设没有对同一对象的 Update()
并发调用,但可能对同一对象的 Update()
和 GetAverage( )
。
据我所知,GetAverage()
中的 m_count
的获取负载禁止对其之前的 m_accumulatedValue
的负载重新排序并且同时保证Update()
对m_accumulatedValue
执行的任何更改对调用GetAverage()< 的线程可见
一旦对 m_count
的更改也被看到,因为 Update()
在 m_cout
上执行的存储有一个发布顺序。
我刚才说的对吗?
GetAverage()
(保证调用 Update()
的非并发性)是否总是返回正确答案?或者有一种方法可以返回计算出的平均值,其中一些值比其他值“更更新”?
m_accumulatedValue
是否需要原子性?
最佳答案
您对获取/释放语义如何工作的描述是正确的; 它们用于在存储/释放之前和加载/获取之后的内存操作之间创建线程间happens-before关系... 这是基于运行时关系,仅在原子加载/获取看到存储/发布设置的值时定义。
您的代码的第一个问题是它无法满足此运行时要求。
m_count
的值未被检查,因此排序保证不适用;因此,您也可以对所有操作使用 memory_order_relaxed
。
但这并不能解决问题;当您读取 m_accumulatedValue
时,它的值可能已通过对 Update()
的另一次调用再次更改(因此 m_accumulatedValue
必须是原子的)。
此外,正如评论部分指出的那样,由于原子操作之间没有原子性,因此可能会在 Update()
完成之前调用 GetAverage()
并返回错误值(value)。
您需要在 Update()
和 GetAverage()
之间严格排序,最好的方法是使用 std::mutex
。然后,原子变量可以是常规整数(如果未在其他地方使用)。
关于c++ - 发布获取语义以计算平均值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42007362/