美文网首页
iOS GCD 串行(serial)/并发(concurrent

iOS GCD 串行(serial)/并发(concurrent

作者: iOS坚持者 | 来源:发表于2019-06-20 22:40 被阅读0次

欢迎私信探讨。

  • 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,又开启新的线程去执行新的任务。

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

相关文章

  • iOS GCD 串行(serial)/并发(concurrent

    欢迎私信探讨。 GCD中队列分为两种串行队列:串行队列的特点是队列中的任务会一个一个的执行,前一个任务不执行完成下...

  • Grand Central Dispatch(GCD) 深入浅出

    1 GCD 术语 1.1 Serial vs. Concurrent 串行 vs. 并发 概念:该术语描述执行当前...

  • 多线程 GCD

    GCD Serial vs. Concurrent 串行 vs. 并发 这些术语描述当任务相对于其它任务被执行,任...

  • GCD多线程归纳和总结

    GCD多线程基础知识 Serial Dispatch Queue 串行队列Concurrent Dispatch ...

  • iOS面试8 - 多线程

    GCD 同步异步, 串行并发同步串行: dispatch_sync(serial_queue, ^{ // wor...

  • GCD串行队列与并发队列

    GCD的队列可以分为2大类型:串行队列、并发队列。一: 串行队列(Serial Dispatch Queue):一...

  • GCD 的使用

    Serial vs. Concurrent 串行 vs. 并发 这些术语描述当任务相对于其它任务被执行,任务串行执...

  • 吃个快餐都能学到串行、并行、并发?

    这篇文章来唠唠概念,讲这三兄弟:串行(Serial)、并行(Parallel)、并发(Concurrent)。 0...

  • iOS 多线程

    串行(Serial):在固定时间内只能执行单个任务。例如主线程,只负责 UI 显示。 并发(Concurrent)...

  • iOS开发多线程之GCD

    iOS开发多线程之GCDiOS开发之GCD同步任务加强iOS开发之GCD串行队列iOS开发之GCD并发队列 GCD...

网友评论

      本文标题:iOS GCD 串行(serial)/并发(concurrent

      本文链接:https://www.haomeiwen.com/subject/lagkqctx.html