2.5 lazy initialization
文章目錄
- 保護共享數據的初始化過程
- std::call_once
- std::call_once 的替代方案
保護共享數據的初始化過程
lazy initialization (延遲初始化)在單線程的代碼中是很常見的。譬如一個共享數據的初始化構建可能會消耗較多的資源,那么對它的每次操作都需要先對它進行檢查,如果它已經被初始化了,那么就可以直接使用而不是重新再初始化它。
例如,打開一個文件可能需要消耗較多的資源,如果它已經被打開,那么我們就不需要再次 open 了。
上面的例子并不是線程安全的。在并發代碼中假設有A和B兩個線程,當 A 線程發現文件沒有被打開然后打開文件往里寫數據時,B 線程此時應該是不能夠打開文件的,但在 B 線程看來,文件確實是被打開了的,因此只要 B 線程搶占到鎖,那么它就會往文件里寫數據。
因此,打開文件的這一步,需要用互斥量保護起來。
從上面的結果我們可以看到,我們所做的修改依然不是線程安全的。因為當線程 A 打開文件后,離開了 mtxFile 的保護域時,線程 B 可能在這個時候拿到了 mtxFile 然后打開文件。為此,我們需要將 is_open 也加入鎖的范圍內
可以看到,上面的這份代碼就是線程安全的。現在我們考慮性能上的問題。我們頻繁地創建鎖并加鎖解鎖操作會消耗大量的系統資源,C++標準庫為此提供了較好的解決方案。
std::call_once
與 std::call_once 相適配的是 std::once_flag,它會比互斥量消耗的資源更少,特別是當初始化完成以后。
- std::mutex 和 std::once_flag 對象不能拷貝和移動。
于是,方案修改為:
使用 std::call_once 可以確保 lambda 表達式在并發程序中只會被一個線程調用一次,這就是我們所說的 lazy initialization。
通常,我們也會將 std::call_once 和要保護的數據放在一個類中,用來延遲初始化。借用書中的代碼:
在本例中,第一次調用 send_data 和 receive_data 時會完成線程的初始化。
std::call_once 的替代方案
在只需要一個全局實例的情況下,多線程可以安全地調用 getInstance 函數,不用擔心數據競爭。
總結
以上是生活随笔為你收集整理的2.5 lazy initialization的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用restTemplate上传图片
- 下一篇: 浅谈802.15.4协议