SDWebImage学习笔记之dispatch_sync

前言

之前学习GCD的时候,在很多文章中看到过这段段代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

结果只会输出1,并造成主线程死锁。这些文章对死锁的原因也做了解释,且只要把dispatch_sync改为dispatch_async,就可以输出顺序为1->3->2的结果。

当时以为自己学会了,但是等到用的时候还是一头雾水,之前理解的东西就只是些皮毛。趁着这次学习SDWebImage库,重温了这段代码,才明白自己当时的问题所在。

误区

上述这段代码用来表明死锁的方式没有错,但是对我造成的误区是,笔者以为导致死锁的代码是NSLog(@"2")和NSLog(@"3")。dispatch_sync会阻塞主线程,必须等待NSLog(@"2")执行完后NSLog(@"3")代码才可以执行("3"等待"2")。但是NSLog(@"3")先于NSLog(@"2")被插入到主线程队列中去,NSLog(@"2")必须等到NSLog(@"3")执行完才可以执行("2"等待"3"),这种相互等待的过程导致了主线程死锁。

这种理解方式一直让我深信不疑,但是一次偶然的机会,看到了下面这段代码后,才明白原来的理解方式是错误的。(笔者认为,下面这段代码更能够表现死锁问题。)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
}

这段代码没有NSLog(@"1"),也没有NSLog(@"3"),但是依然会造成死锁,顿时我就疑惑了,为什么没有NSLog(@"3")还会有问题呢?

解惑

在解惑之前,先来看dispatch_sync方法的描述。苹果官方对它的描述是:

Submits a block object for execution on a dispatch queue and waits until that block completes.

翻译过来就是:

提交一个块对象以在分派队列上执行,并等待该块完成。

dispatch_sync方法有两个参数:queue和block。queue表示用于执行block的队列。block是一个代码块,包含了要队列中执行的代码。

队列有三种:

  1. 主队列。主队列运行在主线程中,是一个串行队列。
  2. 串行队列。遵循先进先出(FIFO)的原则,每次只能执行一个操作。
  3. 并发队列。遵循先进先出(FIFO)的原则,每次可以执行多个操作。

队列和执行方式的关系如下表所示:

同步 异步
串行队列(主队列) 在主线程中执行 在主线程中执行
串行队列(非主队列) 在当前线程中执行 在新建线程中执行
并发队列 在当前线程中执行 在新建线程中执行

dispatch_sync是同步执行方法,会等待第一个参数queue执行完第二个参数block中的代码后,才可以执行dispatch_sync后面的代码。现在,回头再来看上面的代码。

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
}

dispatch_sync方法定义在viewDidLoad方法内,viewDidLoad在主线程中执行,所以dispatch_sync方法也在主线程中执行。

dispatch_sync方法的第一个参数传入的时候主队列,说明第二个参数传入的block中的代码也在主线程中运行。

下面这段话很关键!
下面这段话很关键!
下面这段话很关键!

dispatch_sync方法等待queue中的block执行,queue是主队列。但是,由于主队列是串行队列,dispatch_sync比block先加入到主队列中,所以block要等待dispatch_sync执行完才可以执行,这样相互等待的方式最终导致了死锁。

至此,我心中的疑云被解开了,dispatch_sync和block的相互等待才是导致主线程死锁的“真凶”!

解决方法

解决方法有很多,常见的有以下两种:

  1. 将dispatch_sync替换成dispatch_async,即将同步执行改为异步执行。dispatch_async方法会立即返回,允许后面的代码执行,且dispatch_async的block会插入到主队列的末尾,等到后面的代码执行完毕后,再执行block。
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"2");
    // 打印当前队列的标签
    NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
// print 2
// print com.apple.main-thread 主队列标签
  1. 创建一个串行队列(非主队列)或并发队列。创建了一个新的队列,不会影响主队列执行,因为是同步执行方法,所以主队列会等到新队列执行完block后才继续执行。
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", nil);
// 并发队列
// dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue,  nil), ^{
    NSLog(@"2");
    // 打印当前队列的标签
    NSLog(@"%s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});

// print 2
// print queue 串行队列标签

以上两种解决方法,代码依然运行在主线程,前一种方法改为了异步执行,后一种方法创建新的队列。


SDWebImage中的一段宏定义

#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {\
        block();\
    } else {\
        dispatch_async(queue, block);\
    }
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

dispatch_queue_async_safe方法接收两个参数queue和block,该方法判断传入queue的标签是否等于当前队列的标签,返回YES,则在queue(当前队列)中执行block,否则将block添加到queue中异步执行。

dispatch_main_async_safe方法接收一个参数block,内部调用dispatch_queue_async_safe方法,传入queue的值为主队列dispatch_get_main_queue()和block,dispatch_queue_async_safe方法判断当前队列是否为主队列,返回YES则直接执行block的代码,否则将block添加到主队列中异步执行。

这个宏可以保证,block中的代码一定在主队列中执行。


总结

其实理解dispatch_sync的关键就是搞清楚执行代码所在的队列是哪个,只要执行dispatch_sync的队列跟dispatch_sync第一个参数传入的队列相同,就会产生死锁。

还有,看苹果的官方文档是最正确的方式,虽然英文看上去累一些,但是能够准确表达每一个类,每一行代码的意义。中文翻译过来的语言都会带上作者自己的一些理解。官方文档对于GCD的描述如下:

GCD provides and manages FIFO queues to which your application can submit tasks in the form of block objects. Work submitted to dispatch queues are executed on a pool of threads fully managed by the system. No guarantee is made as to the thread on which a task executes.

请认真研读下面这句话,会对理解调度队列和线程之间的关系有帮助。

Work submitted to dispatch queues are executed on a pool of threads fully managed by the system.

GCD官方文档地址:https://developer.apple.com/documentation/dispatch#//apple_ref/doc/uid/TP40008079

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容