std :: call_once,应何时使用?

dar*_*une 7 c++ c++17

std::call_once https://en.cppreference.com/w/cpp/thread/call_once

确保以线程安全的方式恰好一次调用了callable。

由于还有其他类似的方法,因此问题是:

什么时候应该使用?它打算解决什么类型的问题?

请提供示例。

The*_*ist 9

示例:我将它用于libcURL来从网站检索http(s)数据。在libcURL中,必须先进行一次全局初始化,然后才能使用该库。鉴于初始化不是线程安全的,但是从网站请求数据是线程安全的call_once,因此无论在哪个线程中以及是否同时调用它,我都只调用一次初始化。

  • @einpoklum `static` 和 `std::call_once` 在这种情况下并不相同。`std::call_one` 的状态*由开发人员控制*。在我的示例中,您可能有许多不同的地方必须调用初始化,但必须仅由想要首先执行请求的人调用一次。如果您的执行路径定义良好,静态将对您有所帮助,但情况并非总是如此,尤其是在 GUI 应用程序之类的情况下。 (3认同)

Fab*_*bio 6

典型用途是当您希望在可能发生争用(多线程)的情况下按需初始化全局数据。

假设你有结构

struct A{ A() {/*do some stuff*/} };
Run Code Online (Sandbox Code Playgroud)

并且你想要一个在全局范围内的实例。

如果您按如下方式进行,它会在 main 之前初始化,因此它不是按需的。

A a_global;
Run Code Online (Sandbox Code Playgroud)

如果您按照以下方式进行操作,则它是按需提供的,但它不是线程安全的。

A *a_singleton = NULL;
A *getA() { 
   if (!a_singleton)
      a_singleton = new A();
   return a_singleton;
}
Run Code Online (Sandbox Code Playgroud)

call_once解决了这两个问题。当然,您可以使用其他同步原语的某种组合来代替,但最终只能重新实现您自己的call_once.

  • @Fabio:我相信你错了;也就是说,局部静态确实是按需初始化的,但 C++11 标准要求此初始化是线程安全的。请参阅[这篇文章](/sf/answers/567150181/)。 (2认同)

Ben*_*Ben 6

缓存和惰性评估。假设一个不可变类有一个存储成本低但计算成本高的属性,double foo() const;。您可以这样做,而不是按需计算或预先计算

private:
    mutable std::once_flag m_flag;
    mutable double m_foo;
    double doCalcFoo() const; // Expensive!
public:
    double foo() const {
        std::call_once(m_flag, [this] { m_foo = doCalcFoo(); });
        return m_foo;
    }
Run Code Online (Sandbox Code Playgroud)

虽然你可以做

private:
    mutable std::optional<double> m_foo;
    mutable std::mutex m_fooMutex;
    double doCalcFoo() const; // Expensive!
public:
    double foo() const {
        std::lock_guard lock{m_fooMutex};
        if (!m_foo) {
            m_foo = doCalcFoo();
        }
        return *m_foo;
    }
Run Code Online (Sandbox Code Playgroud)

这是更多的字节(40 + 16 = 56 字节与 Clang 上的 4 + 8 + padding = 16 相比),性能较差,并且违反了 Parent 的“无原始同步基元”的更好代码目标:( https://sean -parent .stlab.cc/presentations/2016-08-08-concurrency/2016-08-08-concurrency.pdf幻灯片 6 至 11)。


Fan*_*Fox 5

想象一个带有一些巨大数据的单例实例(出于某种原因):

class Singleton {
    public:  static Singleton& get();
    ...
    private: static std::unique_ptr<SingletonDataBase> instance;
}
Run Code Online (Sandbox Code Playgroud)

我们如何确保 get 函数在正确调用时创建实例(无论出于何种原因,它都非常大并且不能进入静态内存空间)。我们如何实现这一目标?

  1. 使用mutex? 我猜有点丑。
  2. 使用std::call_once? 更好,并且坚定地给出了代码的意图:

Singleton& Singleton::get() {
    static std::once_flag flag;
    std::call_once(flag, [&](){ instance.reset(new SingletonDataBase()); });
    return instance.get_interface()
}
Run Code Online (Sandbox Code Playgroud)

每当您需要只调用一次时,使用call_once.