C++ 双重检查锁定在C+中是否安全+;用于单向数据传输?

C++ 双重检查锁定在C+中是否安全+;用于单向数据传输?,c++,multithreading,performance,c++11,double-checked-locking,C++,Multithreading,Performance,C++11,Double Checked Locking,我继承了一个我正试图提高性能的应用程序,它目前使用互斥体(std::lock\u guard)将数据从一个线程传输到另一个线程。一个线程是低频(慢速)线程,它只是修改另一个线程使用的数据 另一个线程(我们称之为fast)有相当严格的性能要求(它需要尽可能达到每秒最大的循环次数),我们相信这会受到互斥锁使用的影响 基本上,当前的逻辑是: slow thread: fast thread: occasionally: very-often:

我继承了一个我正试图提高性能的应用程序,它目前使用互斥体(
std::lock\u guard
)将数据从一个线程传输到另一个线程。一个线程是低频(慢速)线程,它只是修改另一个线程使用的数据

另一个线程(我们称之为fast)有相当严格的性能要求(它需要尽可能达到每秒最大的循环次数),我们相信这会受到互斥锁使用的影响

基本上,当前的逻辑是:

slow thread:             fast thread:
    occasionally:            very-often:
        claim mutex              claim mutex
        change data              use data
        release mutex            release mutex
为了让快速线程以最大吞吐量运行,我想尝试删除它必须执行的互斥锁的数量

我怀疑双重锁定检查模式的变化可能在这里有用。我知道它在双向数据流(或单例创建)方面存在严重问题,但在我的例子中,责任范围在哪个线程对共享数据执行哪些操作(以及何时)方面有点有限

基本上,慢速线程会设置数据,并且不会再次读取或写入数据,除非出现新的更改。快速线程使用并更改数据,但从不希望将任何信息传递回另一个线程。换句话说,所有权基本上是单向流动的

我想看看是否有人能在我考虑的策略中找出漏洞


新的想法是有两组数据,一组是当前数据,另一组是待处理数据。在我的例子中,不需要队列,因为传入的数据会覆盖以前的数据

挂起的数据只会在互斥锁的控制下由慢速线程写入,并且它将有一个原子标志来指示它已经写入并放弃了控制(目前)

快速线程将继续使用当前数据(没有互斥),直到设置原子标志为止。由于它负责将挂起数据传输到当前数据,因此可以确保当前数据始终一致

在设置标志的位置,它将锁定互斥锁,并将挂起传输到当前,清除标志,解锁互斥锁并继续

因此,基本上,快速线程以全速运行,只有在知道需要传输挂起的数据时,互斥锁才会锁定


进入更具体的细节,该类将具有以下数据成员:

std::atomic_bool m_newDataReady;
std::mutex       m_protectData;
MyStruct         m_pendingData;
MyStruct         m_currentData;
在慢线程中接收新数据的方法是:

void NewData(const MyStruct &newData) {
    std::lock_guard<std::mutex> guard(m_protectData);
    m_newDataReady = false;
    Transfer(m_newData, 'to', m_pendingData);
    m_newDataReady = true;
}
void NewData(const MyStruct&NewData){
std::锁紧保护装置(m_protectData);
m_newDataReady=false;
传输(m_newData,'to',m_pendingData);
m_newDataReady=true;
}
清除该标志可防止快速线程在即时传输操作完成之前甚至尝试检查新数据

快速线程有点棘手,使用标志将互斥锁保持在最小值:

while (true) {
    if (m_newDataReady) {
        std::lock_guard<std::mutex> guard(m_protectData);
        if (m_newDataReady) {
            Transfer(m_pendingData, 'to', m_currentData);
            m_newDataReady = false;
        }
    }

    Use (m_currentData);
}
while(true){
如果(m_newDataReady){
std::锁紧保护装置(m_protectData);
如果(m_newDataReady){
传输(m_pendingData,'至',m_currentData);
m_newDataReady=false;
}
}
使用(m_currentData);
}
现在在我看来,在快速线程中使用此方法可以大大提高性能:

  • 只有一个地方的原子标志是在互斥对象的控制之外使用的,而且它是一个原子,这意味着它的状态在那里应该是一致的
  • 即使它不一致,互斥锁锁定区域内的第二个检查也应该提供一个安全阀(当我们知道它一致时会重新检查)
  • 数据传输仅在互斥锁的控制下执行,因此应始终保持一致
  • 快速线程中的外部循环意味着将避免不必要的互斥锁——只有当标志为true(或“半true”,可能是不一致的状态)时,才会执行互斥锁
  • 内部
    if
    将处理在检查互斥锁和锁定互斥锁之间清除标志的“半真”可能性
我看不出这个策略有什么漏洞,但是,考虑到我刚刚进入标准C++世界的原子/线程领域,我可能遗漏了一些东西


使用这种方法是否有明显的问题?

尽管如此,您实际看到了多少改进?如果是在英特尔处理器上,则默认情况下原子的设置顺序一致,您不会面临任何排序问题,但我不确定其他体系结构上的默认行为。内部
if
是断言。这不是更像一个不变的检查吗?至少在调试版本上它应该是一个断言。@Arunmu,改进相当不错,大约8%,但我不希望结果是不确定的,可能会崩溃的代码:-)它实际上是在ARM(iMX6)而不是Intel上运行的。实际上,我不希望它在内部
中断言if
(或者对发布版本不做任何操作),这实际上是为了防止尝试传输,如果慢线程在快线程执行更改之前偷偷进入以取消更改。这在简化的示例中没有显示,但在最终的解决方案中是可能的。好的。最好明确地提供原子变量的加载和存储语义,使其顺序一致。您可能会使用稍微弱一点的获取-释放内存顺序,但这对我来说是一个未知领域。否则,这个解决方案对我来说看起来很安全,有点让我想起了危险指针:)对于所有这些,你实际看到了多少改进?如果是在英特尔处理器上,那么原子的默认设置是顺序一致的,你不会面临任何排序问题,但我不确定其他体系结构上的默认行为。内部
if
是断言。这不是更像一个不变的检查吗?它至少应该是调试版本上的断言。@Arun