【问题标题】:What's the best way to run an expensive initialization?运行昂贵的初始化的最佳方法是什么?
【发布时间】:2015-07-17 03:10:26
【问题描述】:

我有一个初始化成本很高的单例:

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() 时,如何最大程度地减少延迟时间?我有很多核心......

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    一旦您的应用程序启动,并且(希望)在您真正需要调用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 被破坏,但操作系统线程继续存在,运行get_X 直到完成。

      thread 被标准化时,有人认为detached threads 不仅没用,而且很危险。但对于detached threads,这是一个非常安全且非常激励人心的用例。

    • 如果我的应用程序在detached thread 完成对get_X() 的第一次调用之前调用get_X() 怎么办?

      会影响性能,但不会影响正确性。您的应用程序将在get_X() 中的这一行阻塞:

      static const X x = init_X();

      直到detached thread 完成执行。因此不存在数据竞争。

    • 如果我的申请在detached thread 完成之前结束怎么办?

      如果您的应用程序在初始化阶段结束,那么显然出现了灾难性的错误。如果get_X 触及已经被at_exit 链(在main 之后执行)破坏的东西,就会发生不好的事情。但是,您已经处于恐慌关闭状态……再发生一次紧急情况不太可能使您的恐慌关闭变得更糟。你已经死了。 Otoh,如果您的初始化需要几分钟到几小时,您可能确实需要更好地沟通初始化何时完成,以及更优雅的关闭程序。在这种情况下,您需要在线程中实现协作取消,无论是否分离(std 委员会拒绝向您提供)。

    • 如果第一次调用get_X() 引发异常怎么办?

      在这种情况下,对get_X() 的第二次调用有机会初始化函数本地静态,假设您没有留下未捕获的异常并允许它终止您的程序。它可能也会抛出,或者它可能会成功初始化(这取决于您的代码)。在任何情况下,对get_X() 的调用将继续尝试初始化,等待初始化是否正在进行,直到有人设法这样做而不抛出异常。无论调用是否来自不同的线程,这都是正确的。

    总结

    std::thread(get_X).detach();
    

    是一种很好的方式,可以利用您的多核的力量,在不影响线程安全正确性的情况下,尽快摆脱昂贵的独立初始化。

    唯一的缺点是不管你是否需要,你都会在get_X() 中初始化数据。因此,请确保在使用此技术之前需要它。


    [脚注] 对于那些使用 Visual Studio 的人来说,这是迁移到 VS-2015 的良好动力。在这个版本之前,VS 没有实现线程安全的函数局部静态。

    【讨论】:

    • 加一个,因为我学到了一些新东西。好吧,我可能会在遇到问题后的某个时候想到它。但是,很高兴知道。 :)
    • 如果初始化可以抛出,你可能需要将它包装在 try/catch 中以避免terminate?
    • “没有数据竞争”——只要您使用的编译器实际上已经为函数局部静态初始化实现了 C++11 线程安全。
    • @TheUndeadFish:谢谢,添加了关于 VS 的脚注。
    【解决方案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;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-20
      • 2012-01-28
      • 2016-12-12
      • 1970-01-01
      • 2013-06-16
      相关资源
      最近更新 更多