簡體   English   中英

std::call_once,什么時候用?

[英]std::call_once, when should it be used?

C++11 中引入的std::call_once函數確保以線程安全的方式只調用一次可調用對象。

由於這可以通過其他方式實現 - 什么時候應該使用std::call_once 它打算解決什么類型的問題?

請舉例說明。

示例:我將它用於 libcURL 以從網站檢索 http(s) 數據。 在 libcURL 中,您必須先進行一次全局初始化,然后才能使用該庫。 鑒於初始化不是線程安全的,但從網站請求數據是線程安全的,我使用call_once只調用一次我的初始化,無論在哪個線程中以及是否並發調用。

想象一個帶有一些巨大數據的單例實例(出於某種原因):

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

我們如何確保 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()
}

每當您需要只調用一次時,使用call_once很好。

典型用途是當您希望在可能發生爭用(多線程)的情況下按需初始化全局數據。

假設你有結構

struct A{ A() {/*do some stuff*/} };

並且你想要一個在全局范圍內的實例。

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

A a_global;

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

A *a_singleton = NULL;
A *getA() { 
   if (!a_singleton)
      a_singleton = new A();
   return a_singleton;
}

call_once解決了這兩個問題。 當然,您可以使用其他同步原語的某種組合來代替,但最終只會重新實現您自己的call_once版本。

緩存和惰性求值。 假設一個不可變類有一個存儲成本低但計算成本高的屬性, double foo() const; . 您不是按需計算或mutable std::once_flag m_flag; mutable double m_foo;計算,而是添加mutable std::once_flag m_flag; mutable double m_foo; mutable std::once_flag m_flag; mutable double m_foo; 然后可以做

double foo() const {
    std::call_once(m_flag, [this] { m_foo = doCalcFoo(); });
    return m_foo;
}

什么時候應該使用?

當你想調用一次時。 關於它在做什么,它簡潔明了。

替代方案

struct CallFooOnce { 
    CallFooOnce() { 
        foo(); 
    } 
}; 
static CallFooOnce foo_once;

有更多的樣板,並引入了一個額外的名稱,超過

static std::once_flag foo_once;
std::call_once(foo_once, foo);

其他答案已經提到call_once效果可以通過魔術靜態或mutex使用來實現。

我想專注於性能和極端情況處理。

在快速路徑上使用mutexcall_once(當目標函數已經被調用時)。 那是因為mutex獲取/釋放將是原子 RMW,而call_once查詢只是讀取。 您可以通過實施雙重檢查鎖定來修復mutex性能,但這會使程序復雜化。

如果第一次嘗試僅在程序退出時調用目標函數,則使用call_once也比 mutex 更安全 mutex不一定可以簡單地破壞,因此在調用目標時可能會破壞全局互斥體。

通過只讀取查詢對象是否已經初始化,魔術靜態也可能在快速路徑上很快。 magic static 與call_once的性能可能取決於 implementation ,但通常 magic static 有更多的優化機會,因為它不必在變量中公開狀態並且必須由編譯器實現,而不僅僅是在庫中.

MSVC 特定:魔術靜態使用線程本地存儲。 這比call_once更優化。 但是可以通過/Zc:threadSafeInit-關閉魔法靜態支持。 關閉它的能力是由於一些線程本地存儲的缺點。 call_once不能關閉。 在 Visual Studio 2015 之前, call_once沒有很好地實現,並且根本沒有實現魔術靜態,因此如果沒有 3rd 方庫,很難獲得一次性初始化。

std::call_once()可用於惰性求值,這樣傳遞給它的可調用對象即使有多個線程執行,也只會執行一次。 請參閱下一個示例,其中getInstance()方法可能會被多個線程多次調用,但是該實例僅在需要時創建一次(第一次調用 getInstance())。

#include <mutex>

class Singleton {
  static Singleton *instance;
  static std::once_flag inited;

  Singleton() {...} //private

public:
  static Singleton *getInstance() {
    std::call_once( inited, []() {
        instance=new Singleton();
      });
    return instance;
  } 

};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM