![](/img/trans.png)
[英]Is it possible to capture return value of function used in 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 函數在正確調用時創建實例(無論出於何種原因,它都非常大並且不能進入靜態內存空間)。 我們如何實現這一目標?
mutex
? 我猜有點丑。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
使用來實現。
我想專注於性能和極端情況處理。
在快速路徑上使用mutex
會比call_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.