一個(gè)dispatch_once死鎖問(wèn)題

一個(gè)dispatch_once死鎖問(wèn)題

工作中遇到一問(wèn)題:
我們有一個(gè)埋點(diǎn)SDK,
啟動(dòng)app,SDK的單例類(lèi)的初始在主線程執(zhí)行 [[Sdk shareInstance] startWithConfig]
同時(shí)有一個(gè)業(yè)務(wù)方法在子線程調(diào)用該sdk的方法去埋點(diǎn),埋點(diǎn)方法也是調(diào)
[[Sdk shareInstance] addEvent:];

在[Sdk shareInstance]的方法里會(huì)出現(xiàn)一定概率的死鎖!!

先看看模擬代碼

通常我們用dispatch_once寫(xiě)單例如下:
注意一下,我們?cè)趇nit方法里面調(diào)用到一個(gè)切到主線程的方法ksk_syncRunOnMainQueue, 在主線程中做了點(diǎn)事情。


#import "SSTestManager.h"

NS_INLINE void ksk_syncRunOnMainQueue(void (^block)(void)) {
    if (!block) return;
    if ([NSThread isMainThread]) {
        block();
    } else {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

@implementation SSTestManager

+ (instancetype)sharedInstance {
    static SSTestManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self.class alloc] init];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        [self observeAppLife];
    }
    
    return self;
}

- (void)observeAppLife {
    ksk_syncRunOnMainQueue(^{
        // do something on main thread
        NSLog(@"test");
    });
}

@end

調(diào)用的地方,在主線程中

@implementation QTGCDIndexController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 當(dāng)前主線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [SSTestManager sharedInstance];
    });
    
    // 讓主線程休息1s
    // sleep(1); 
    
    [SSTestManager sharedInstance];
}


@end

模擬重現(xiàn)步驟:

  1. 正常執(zhí)行,沒(méi)有問(wèn)題。執(zhí)行順序是先主線中調(diào)用[SSTestManager sharedInstance], 然后子線程中執(zhí)行
  2. 放開(kāi)sleep(1), 讓子線程先執(zhí)行起來(lái)[SSTestManager sharedInstance],主線程再執(zhí)行,發(fā)現(xiàn)死鎖了,為什么???思考思考,...

問(wèn)題分析:

  1. 正常執(zhí)行,主線程完成sharedInstance之后,子線程再開(kāi)始執(zhí)行,所以子線程同步向主線提交任務(wù),dispatch_sync(dispatch_get_main_queue(), block),主線程沒(méi)有等待任務(wù),直接在主線程執(zhí)行。
  2. 放開(kāi)sleep之后,子線程在執(zhí)行sharedInstance中,調(diào)用dispatch_sync(dispatch_get_main_queue(), block)同步向主線程提交任務(wù),它要等待主線程任務(wù)處理完,再執(zhí)行當(dāng)前block,而同時(shí)主線程執(zhí)行sharedInstance方法,由于dispatch_once正在被子線程鎖,此時(shí)主線程block等待子線程完成。這樣,就造成了互相等待,死鎖發(fā)生了。。

結(jié)論:

  1. dispatch_once中的代碼盡量只做初始化的事情,不要調(diào)用很多其他的方法。
  2. dispatch_once盡量不要切換線程,特別是同步提交到其他線程的任務(wù)。
  3. dispatch_once中的代碼盡量不要拋出異常,不要Crash。
  4. 盡量能保持自給自足,減少對(duì)別的模塊或者類(lèi)的依賴(lài);

dispatch_once死鎖方式

死鎖方式1:
1、某線程T1()調(diào)用單例A,且為應(yīng)用生命周期內(nèi)首次調(diào)用,需要使用dispatch_once(&token, block())初始化單例。
2、上述block()中的某個(gè)函數(shù)調(diào)用了dispatch_sync_safe,同步在T2線程執(zhí)行代碼
3、T2線程正在執(zhí)行的某個(gè)函數(shù)需要調(diào)用到單例A,將會(huì)再次調(diào)用dispatch_once。
4、這樣T1線程在等block執(zhí)行完畢,它在等待T2線程執(zhí)行完畢,而T2線程在等待T1線程的dispatch_once執(zhí)行完畢,造成了相互等待,故而死鎖

死鎖方式2:
1、某線程T1()調(diào)用單例A,且為應(yīng)用生命周期內(nèi)首次調(diào)用,需要使用dispatch_once(&token, block())初始化單例;
2、block中可能掉用到了B流程,B流程又調(diào)用了C流程,C流程可能調(diào)用到了單例A,將會(huì)再次調(diào)用dispatch_once;
3、這樣又造成了相互等待。

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

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