探索dispatch_once

dispatch_once

dispatch_once是我們都所熟知的,它的用途就像名字一樣,執(zhí)行某個事件一次且僅只有一次。
它只有兩個參數(shù),第一個參數(shù)是once,第二個是首次執(zhí)行的block塊,調(diào)用起來是這樣的

static dispatch_once_t predicate;
 dispatch_once(&predicate, ^{ 
       // some one-time task
 });

這個API用于全局的dictionary、單例、緩存,或者其他的只需要執(zhí)行一次的任務(wù)。在一個單線程中,這種調(diào)用可能意義并不是很大,可以用一個簡單的if語句來代替,但是我們的應(yīng)用程序往往都是多線程的,dispatch_once是線程安全的,它可以保證在多個線程同時調(diào)用的時候,dispatch_once只會執(zhí)行一次,并且所有的線程在dispatch_once返回之前都會進入等待狀態(tài)。雖然我們自己來實現(xiàn)并不是難事,但是dispatch_once的速度是非常的快的,效率很高

dispatch_once在單線程的實現(xiàn)

讓我們先來看看dispatch_once在單線程中是怎么實現(xiàn)的,這在實際開發(fā)中是意義并不大,但是讓我們對它的理解有更好的幫助。注意,dispatch_once是由一個longtypedef來進行控制的,初始化為0,這里是它的實現(xiàn):

void SimpleOnce(dispatch_once_t *predicate, dispatch_block_t block) {
    if(!*predicate) {
        block(); *predicate = 1;
    }
}

它的實現(xiàn)非常的簡單,如果predicate為0,block進行回調(diào),并把predicate設(shè)置為1。在后續(xù)的執(zhí)行中將不會再調(diào)用block,這正是我們想要的,但是這在多線程下是完全不安全的,兩個線程可以同時執(zhí)行到if語句,會多次調(diào)用到block,這種情況就非常糟糕,并不是我們想要的結(jié)果。
在平時開發(fā)的時候這是常有的情況,那么我們就得考慮到線程安全,往往線程安全的代碼會給性能帶來隱患。

性能問題

當(dāng)我們談到dispatch_once的性能的時候,我們實際上從三種不同的方面來進行考慮

  1. 當(dāng)?shù)谝淮螆?zhí)行dispatch_once,傳入predicate參數(shù),由哪個線程來執(zhí)行block
  2. 在首次調(diào)用dispatch_once之后,在block執(zhí)行完畢之前,調(diào)用者不得不等待block執(zhí)行完畢再繼續(xù)執(zhí)行
  3. dispatch_once首次執(zhí)行完成之后并且block也執(zhí)行完成了,是無需等待的,是可以立即繼續(xù)執(zhí)行的

第一條在很大程度上是不重要的,畢竟它只會發(fā)生一次。
第二條的性能問題也是同樣重要的,它可能潛在的發(fā)生幾次,但是它只是在block正在執(zhí)行的時候才會發(fā)生。大多數(shù)情況下,是永遠不會發(fā)生的,也可能只會發(fā)生一次,即使在強度測試下,有許多線程同時調(diào)用dispatch_once,那么block將會在執(zhí)行上耗時,這些調(diào)用都會等待block執(zhí)行完成,所以就消耗了一些不必要的CPU
第三條性能問題是及其重要的。發(fā)生這種情況可能會成百上千次,我們希望dispatch_once用于保護一次性執(zhí)行一個計算,這個計算結(jié)果是在任何地方都可以使用的。在理想情況下,堅持用dispatch_once去計算,計算成本應(yīng)該提前于明確的計算時間,計算結(jié)果會賦值在一個全局變量上,我們希望這兩塊代碼有相同的執(zhí)行:

id gObject;

void Compute(void) {
    gObject = ...;
}

id Fetch(void) {
    return gObject;
}

id DispatchFetch(void) {
    static id object;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        object = ...;
    });
    return object;
}

鎖(Locks)

使線程安全的標(biāo)準(zhǔn)方法是用鎖包圍訪問共享數(shù)據(jù)的區(qū)域,這是一個很你難的問題,因為沒有一個很適合的地方放置這個鎖來保護臨近的數(shù)據(jù)
我們可以通過clang的命令看看它的實現(xiàn)原理,可以看到一個包含鎖的struct

未完待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容