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