我经常遇到这个线程安全结构的设计。如下面的 version1 ,一个线程可能很少调用foo1::add_data()
,另一个线程通常调用foo1::get_result()
。出于优化的目的,我认为它可以使用原子来应用双重检查锁定模式(DCLP),如 version2 所示。这种情况还有其他更好的设计吗?或者它是否可以改进,例如使用std::memory_order
?
VERSION1 :
class data {};
class some_data {};
class some_result {};
class foo1
{
public:
foo1() : m_bNeedUpdate(false) {}
void add_data(data n)
{
std::lock_guard<std::mutex> lock(m_mut);
// ... restore new data to m_SomeData
m_bNeedUpdate = true;
}
some_result get_result() const
{
{
std::lock_guard<std::mutex> lock(m_mut);
if (m_bNeedUpdate)
{
// ... process mSomeData and update m_SomeResult
m_bNeedUpdate = false;
}
}
return m_SomeResult;
}
private:
mutable std::mutex m_mut;
mutable bool m_bNeedUpdate;
some_data m_SomeData;
mutable some_result m_SomeResult;
};
版本2 :
class foo2
{
public:
foo2() : m_bNeedUpdate(false) {}
void add_data(data n)
{
std::lock_guard<std::mutex> lock(m_mut);
// ... restore new data to m_SomeData
m_bNeedUpdate.store(true);
}
some_result get_result() const
{
if (m_bNeedUpdate.load())
{
std::lock_guard<std::mutex> lock(m_mut);
if (m_bNeedUpdate.load())
{
// ... process mSomeData and update m_SomeResult
m_bNeedUpdate.store(false);
}
}
return m_SomeResult;
}
private:
mutable std::mutex m_mut;
mutable std::atomic<bool> m_bNeedUpdate;
some_data m_SomeData;
mutable some_result m_SomeResult;
};
答案 0 :(得分:2)
问题是版本2至少不是线程安全的 根据C ++ 11(和Posix,之前的);你正在访问 可以在没有访问权限的情况下修改的变量 保护。 (双重检查锁定模式是已知的 坏了,看 http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf)。 它可以在C ++ 11中工作(或者不可移植地更早) 使用原子变量,但你写的是什么 未定义的行为。
答案 1 :(得分:1)
我认为通过使用允许许多线程并行读取的“读写锁”,可以实现显着的改进(在代码大小方面以及在简单性和性能方面)。为此目的,Boost提供了shared_mutex
,但从快速浏览看来,this blog article以可移植的方式实现了相同类型的锁定而不需要Boost。
答案 2 :(得分:0)
你说过你经常打电话给get_average,你是否考虑过根据你没见过的数字来计算平均值?&#39;?它将是O(n)而不是O(n ^ 2)。
这就像是
average = (last_average * last_size + static_cast<double>(
std::accumulate(m_vecData.begin() + last_size, m_vecData.end(), 0))) /
m_vecData.size();
它应该给你满意的结果,取决于你的矢量有多大。