iOS GCD 串行(serial)/并发(concurrent) 同步(sync)/异步(async) 详解

欢迎私信探讨。

  • GCD中队列分为两种
    • 串行队列:
      串行队列的特点是队列中的任务会一个一个的执行,前一个任务不执行完成下一个任务就不会执行。
      创建方式:
      dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);
    • 并发队列:
      并发队列的特点是不管你多少的任务都会同时执行,任务一般并发队列和异步方式配对使用。
      创建方式:
      dispatch_queue_t queue = dispatch_queue_create("并发", DISPATCH_QUEUE_CONCURRENT);
  • 线程执行方式也分为两种
    • 同步执行:
      同步执行的特点是,不具备开启新线程能力,且会阻塞当前线程。
      调用方式:
      dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
    • 异步执行:
      异步执行的特点是,具备开启新线程能力,不会阻塞当前线程;但是只是说它具备这个能力,并不是一定会开启子线程,因为在主队列中异步执行依然会是在主线程中执行。
      调用方式:
      dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)

接下来将通过串行并发同步异步穿插介绍其中原理。无特殊说明默认在主线程中

serial sync 执行
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });
    NSLog(@"0000");

输出:

2019-06-19 17:49:34.547 fasfsdfs[21016:325617] 开始----<NSThread: 0x6000018ac140>{number = 1, name = main}
2019-06-19 17:49:36.548 fasfsdfs[21016:325617] 结束----<NSThread: 0x6000018ac140>{number = 1, name = main}
2019-06-19 17:49:36.548 fasfsdfs[21016:325617] 0000

分析

  1. 打印第一句:因为是同步,所以会阻塞主线程去执行 queue 队列中的任务,输出第一句,由打印0000的时间和打印开始----的时间对比可得。
  2. 接下来的输出都是按部就班的打印了,没有什么好深入的。
serial sync 任务中嵌套 sync 任务
    dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
        });
        NSLog(@"结束----%@",[NSThread currentThread]);
    });
    NSLog(@"0000");

输出:

2019-06-19 18:11:26.510 fasfsdfs[21508:339513] 开始----<NSThread: 0x600003ce1540>{number = 1, name = main}

分析:

  1. 输出开始---是正确的,上面分析过
  2. 如果你对死锁有了解或者跑了代码就会发现,程序会crash,因为造成了线程死锁。这一点需要重点讲解一下。

这里发生死锁的原因是:在串行队列中同步任务中嵌套了一个新的同步任务。

根据串行队列中任务是一个一个的执行,同步执行是会阻塞线程的,当执行到第二个dispatch_sync时,它阻塞线程去等待第一个dispatch_sync里面的任务先去执行完,而第二个同步任务却是在第一个同步任务里面,只有第二个同步执行完了才会继续执行接下来的代码,这样你看着我,我看着你,双方都卡住了,就造成了死锁。

serial sync 任务中嵌套 async 任务
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-19 18:37:54.614 fasfsdfs[21713:343984] 开始----<NSThread: 0x6000024b94c0>{number = 1, name = main}
2019-06-19 18:37:56.615 fasfsdfs[21713:343984] 结束----<NSThread: 0x6000024b94c0>{number = 1, name = main}
2019-06-19 18:37:56.615 fasfsdfs[21713:355085] 111----<NSThread: 0x600002426640>{number = 14, name = (null)}

分析:

  1. 串行任务依次执行,当执行到 async 时需要等当前的任务执行完成才回去执行里面的任务,因为是异步的所以不会阻塞线程,且不是在主队列中所以会开起子线程执行,综上所述不会造成死锁。
serial async 任务中嵌套 sync
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
        });
        NSLog(@"结束----%@",[NSThread currentThread]);
    });
    NSLog(@"0000");

输出:

2019-06-20 16:44:02.738 fasfsdfs[34323:745553] 开始----<NSThread: 0x600003558e80>{number = 5, name = (null)}

分析:

  1. 虽然是在异步调用的子线程中执行 sync 任务,但是依然会在调用 synccrash。原理同上,串行队列任务一个个执行,上一个任务不执行完成,下一个任务就不会执行,而 sync 属于任务嵌套中的任务。
serial async 任务中嵌套 async
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-20 17:21:28.635 fasfsdfs[35506:772778] 开始----<NSThread: 0x6000001dde40>{number = 7, name = (null)}
2019-06-20 17:21:30.639 fasfsdfs[35506:772778] 结束----<NSThread: 0x6000001dde40>{number = 7, name = (null)}
2019-06-20 17:21:30.639 fasfsdfs[35506:772778] 111----<NSThread: 0x6000001dde40>{number = 7, name = (null)}

分析:

  1. 串行任务,任务依次执行,异步不会阻塞线程,具备开启线程能力,所以在第二个 async 时会等当前任务执行完成再去执行,由打印的时间和次序对比支持以上结论。
concurrent sync
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });
NSLog(@"0000");

输出:

2019-06-20 17:30:12.118 fasfsdfs[35506:772690] 开始----<NSThread: 0x60000014ee00>{number = 1, name = main}
2019-06-20 17:30:14.118 fasfsdfs[35506:772690] 结束----<NSThread: 0x60000014ee00>{number = 1, name = main}
2019-06-20 17:30:14.118 fasfsdfs[35506:772690] 0000

分析:

  1. 并发队列任务可以同时执行,同步不具备开启线程能力,会阻塞当前线程,由打印数据可以得出结论。
concurrent sync 任务中嵌套 sync
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-20 17:32:13.585 fasfsdfs[35506:772690] 开始----<NSThread: 0x60000014ee00>{number = 1, name = main}
2019-06-20 17:32:13.585 fasfsdfs[35506:772690] 111----<NSThread: 0x60000014ee00>{number = 1, name = main}
2019-06-20 17:32:15.586 fasfsdfs[35506:772690] 结束----<NSThread: 0x60000014ee00>{number = 1, name = main}

分析:

  1. 并发与串行的差别在这里可以提现到淋漓尽致。如果串行这样执行,必然会造成死锁。
  2. 执行第一个 sync 时,阻塞主线程,暂停正在执行的任务,转而去执行sync 里面的任务。
  3. 执行到第二个sync时,如果是串行任务之间会互相等待,而并发却不会,新任务不必等待之前的任务执行完;所以第二个 sync会暂停当前任务去执行自己block 里面的任务。
  4. 任务二执行完成,主线程继续执行接下来的任务,等待两秒,打印输出。
concurrent sync 任务中嵌套 async
dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
            sleep(1);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-20 22:21:33.544 fasfsdfs[35506:772690] 开始----<NSThread: 0x60000014ee00>{number = 1, name = main}
2019-06-20 22:21:33.544 fasfsdfs[35506:811697] 111----<NSThread: 0x600000130c80>{number = 11, name = (null)}
2019-06-20 22:21:35.545 fasfsdfs[35506:772690] 结束----<NSThread: 0x60000014ee00>{number = 1, name = main}

分析:

  1. 如果前面所讲您已经了解明白,我相信这一步不会是问题的。
  2. sync 主线程执行,async 并发队列开启子线程执行新任务

#######concurrent async

    dispatch_queue_t queue = dispatch_queue_create("----", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });
    NSLog(@"0000");

输出:

2019-06-20 22:26:57.317 fasfsdfs[35506:772690] 0000
2019-06-20 22:26:57.317 fasfsdfs[35506:822749] 开始----<NSThread: 0x6000001dc140>{number = 17, name = (null)}
2019-06-20 22:26:59.318 fasfsdfs[35506:822749] 结束----<NSThread: 0x6000001dc140>{number = 17, name = (null)}

分析:

  1. "0000" 打印在 "开始" 之前,我猜测可能是系统内部要创建开辟内存耗时了。
  2. 异步开启子线程
concurrent async 任务中嵌套 sync
dispatch_async(queue, ^{
        
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
            sleep(1);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-20 22:33:16.687 fasfsdfs[35506:825627] 开始----<NSThread: 0x600000130500>{number = 20, name = (null)}
2019-06-20 22:33:16.687 fasfsdfs[35506:825627] 111----<NSThread: 0x600000130500>{number = 20, name = (null)}
2019-06-20 22:33:19.694 fasfsdfs[35506:825627] 结束----<NSThread: 0x600000130500>{number = 20, name = (null)}

分析:

  1. async 开启子线程,sync 阻塞线程,去执行自己的任务。
    concurrent async 任务中嵌套 async
dispatch_async(queue, ^{
        
        //打印 NSThread为main
        NSLog(@"开始----%@",[NSThread currentThread]);
        dispatch_async(queue, ^{
            NSLog(@"111----%@",[NSThread currentThread]);
            sleep(1);
        });
        sleep(2);
        NSLog(@"结束----%@",[NSThread currentThread]);
    });

输出:

2019-06-20 22:37:48.393 fasfsdfs[35506:830404] 开始----<NSThread: 0x6000001dee80>{number = 21, name = (null)}
2019-06-20 22:37:48.394 fasfsdfs[35506:830834] 111----<NSThread: 0x6000001def40>{number = 22, name = (null)}
2019-06-20 22:37:50.397 fasfsdfs[35506:830404] 结束----<NSThread: 0x6000001dee80>{number = 21, name = (null)}

分析:

  1. 执行到第一个 async,因为是并发,开启一个新线程去执行任务。
  2. 执行到第二个 async,又开启新的线程去执行新的任务。

上面就是 并发/串行, 同步/异步 的各种嵌套分析;如果还有不清楚的读者欢迎私信我。

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