我继承了一个我尝试提高性能的应用程序,它目前使用互斥锁(std::lock_guard<std::mutex>
)将数据从一个线程传输到另一个线程。一个线程是低频(慢速)线程,它只是修改另一个线程要使用的数据。
另一个线程(我们将快速调用)具有相当严格的性能要求(它需要每秒执行最大周期数)并且我们认为这会受到使用互斥锁的影响。
基本上,目前的逻辑是:
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;
}
清除标志会阻止快速线程甚至尝试检查新数据,直到立即传输操作完成。
快速线程有点棘手,使用标志将互斥锁保持在最低限度:
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);
}
现在在我看来,在快速线程中使用此方法可以提高性能:
if
会照顾到这个&#34; half-true&#34;在检查和锁定互斥锁之间,标志已被清除的可能性。我无法看到这个策略中的任何漏洞但是,鉴于我只是在标准C ++世界中进入原子/线程,它可能是我&#39我错过了什么。
使用此方法有任何明显问题吗?