我有一个算法,效率至关重要,并且90%的时间在单线程模式下运行。现在我们意识到还有另外10%的用例,我们需要使其支持多线程。
可悲的是该算法有一部分,在多线程情况下确实需要互斥保护。这个关键部分重复了数十亿次,因此在单线程中保持互斥锁操作确实是浪费时间。
是否可以将一个类设为“互斥体感知”,以便仅当我们检测到另一个线程正在运行该类实例时才触发互斥体保护? c ++的最佳实践是什么?
更新: 有些人认为这个问题不清楚。我的错。让我尝试更多细节。
系统有90%的时间连续扫描大量数据(将其想象成许多数据框),并将统计信息发送到其他线程,但没有太多细节。然后有时候,如果RPC客户端得到一些有趣的东西,它会要求针对特定范围的盒子提供更多信息。在箱子之间移动非常昂贵,因此在这里不能随意访问。此时,客户端算法将回溯以按计划获取那些特定的框。现在,它是子算法(即处理一个单框)在这里需要互斥量(以保持指针状态,边界处理和许多其他内容,等等)。
因为该算法有90%的时间具有单线程模式,所以我只希望它快速移动,而不必在每个盒子上都加锁。并且,当有一个客户想要寻找并使用单一框算法时,这成为互斥体进入的唯一情况(而且我们只能在运行时知道它)。
答案 0 :(得分:3)
一种方法是使代码以互斥锁类型为模板,然后在单线程情况下传递伪互斥锁。例如这样的东西:
template<typename Mutex>
void foo( Mutex& mutex)
{
std::unique_lock lock(mutex);
// Do stuff
}
然后,您可以在不使用互斥锁的情况下进行呼叫:
std::mutex mutex;
foo(mutex);
struct fake_mutex
{
void lock(){}
void unlock(){}
};
fake_mutex mutex;
foo(mutex);
编译器应将伪互斥优化为很少甚至没有代码。
答案 1 :(得分:0)
这是Read-Copy-Update(RCU)https://lwn.net/Articles/262464/的好用例。 RCU最初被设计为需要OS特权(特别是禁用中断和调度),但是有一个用户空间RCU库URCU https://lwn.net/Articles/573424/。
RCU是一种无锁的范例,并针对读取案例进行了优化。在某些情况下,进入读取端关键部分时,RCU的特权版本不会产生不需要的开销。
我对您实际上是否需要互斥体表示怀疑,我认为您可能可以使用RCU代替互斥体。但是,如果您希望保留互斥锁,则可以使用RCU标志作为锁定互斥锁的条件。这有点不规范,因为通常由互斥锁保护的共享数据应始终使用互斥锁,但是可以,因为RCU保护与互斥锁一样好。
您必须通读许多材料才能理解RCU,但是这里有一个速成课程,介绍了特权的RCU风格。
要进入读取侧关键部分,CPU禁用中断(仅适用于该CPU)。然后,CPU可以读取一个标志,例如if (!locked)
。完成读取侧关键部分后,CPU重新启用中断。
如果线程希望修改某些数据,它将写入标志locked = true
并传播写操作(通过内存屏障)。此时,任何新的读取侧关键部分都将看到标志已锁定,并且避免进入关键部分,但是我们仍然必须担心当前正在进行的读取侧关键部分。因此,修改线程在每个CPU上调度自己。如果另一个CPU位于读取侧关键部分中,则该CPU上的中断将被禁用,因此,在读取侧关键部分完成之前,修改线程将无法在该CPU上运行。返回此锁定函数后,可以保证对数据的独占访问。可以修改数据。
RCU的替代方法是危险指针,它比RCU对偶数读/写访问情况进行了优化。危险指针也是无锁的。危险指针背后的想法是让线程标记其正在使用的事物。我认为您的案例更适合RCU,但可能有更多实现危险指针的库。