我有一个初始化成本很高的单身人士:
struct X {...};
const X&
get_X()
{
static const X x = init_X();
return x;
}
第一次调用get_X()
时,初始化函数本地静态可能需要几百毫秒。但在完成之后,我需要对X
做的事情相对较快:
get_X().find_something_for_me(); // expensive if this is the first call
get_X().find_something_for_me(); // now fast
如何在第一次拨打get_X()
时最大限度地减少延迟?我有很多核心......
答案 0 :(得分:20)
一旦您的应用程序启动,并且(希望)在您实际需要调用get_X()
之前,请无偿地调用它。此外,为了使您的初始化阶段更快,请将昂贵的初始化提供给不同的线程。例如:
#include <thread>
int
main()
{
std::thread(get_X).detach();
// continue with other initialization...
}
当某项任务与几百毫秒(或更多)一样昂贵时,旋转线程来处理它的开销是在噪声级别。如果您使用的是多核硬件(现在还不是什么?),那么如果您的应用程序在初始调用get_X
完成之前实际上并不需要此单例中的任何内容,那么这将是一个明显的性能获胜。
注释/问题:
为什么detach
为thread
?为什么不join
?
如果你决定join
这个thread
,这意味着你只需等待它完成,为什么不做其他事情。完成后,detach
将其清理完毕。您甚至不需要保留thread
的句柄。临时std::thread
析构,但OS线程继续存在,运行get_X
完成。
当thread
被标准化时,有detach
ed thread
s不仅无用而且危险的观点。但对于detach
ed thread
s来说,这是一个非常安全且极具激励性的用例。
如果我的申请在get_X()
ed detach
完成对thread
的第一次通话之前拨打get_X()
该怎么办?
性能受到打击,但未达到正确性。您的应用程序将在get_X()
:
static const X x = init_X();
直到detach
ed thread
完成执行。因此,没有数据竞争。
如果我的申请在detach
ed thread
完成之前结束了怎么办?
如果您的应用程序在初始化阶段结束,那么显然会出现灾难性错误。如果get_X
触及已被at_exit
链(在main之后执行)破坏的东西,则会发生不好的事情。但是,您已经处于恐慌关闭的状态......再一次紧急情况不太可能使您的恐慌情况更加严重。你已经死了Otoh,如果您的初始化需要几分钟到几小时,那么您可能做需要更好地沟通初始化完成时间以及更优雅的关闭过程。在这种情况下,你需要在你的线程中实现合作取消,分离或不分离(std委员会拒绝为你提供的东西)。
如果第一次调用get_X()
会引发异常怎么办?
在这种情况下,对get_X()
的第二次调用有机会初始化函数local static,假设您没有保留未被捕获的异常并允许它终止您的程序。它可能会抛出,或者它可能在初始化中成功(这取决于您的代码)。在任何情况下,对get_X()
的调用将继续尝试初始化,等待初始化正在进行中,直到有人设法这样做而不抛出异常。无论呼叫是否来自不同的线程,都是如此。
总结
std::thread(get_X).detach();
是利用多核功能在不影响线程安全正确性的情况下尽快获得独立昂贵的初始化的好方法。
唯一的缺点是,无论您是否需要,都可以在get_X()
内初始化数据。所以在使用这种技术之前一定要确保它需要它。
[脚注]对于那些使用Visual Studio的人来说,这是转向VS-2015的良好动力。在此版本之前,VS没有实现线程安全的函数 - 局部静态。
答案 1 :(得分:2)
如果您不介意应用程序启动时的延迟,您可以将x
作为该类的静态私有成员,如下所示
#include <iostream>
class X {
public:
static X const& get_x();
private:
static X const x;
X()
{
std::cout << "X init" << std::endl;
}
};
int main()
{
std::cout << "Enter main" << std::endl;
X::get_x();
return 0;
}
X const X::x;
X const& X::get_x()
{
return X::x;
}