美文网首页
iOS 底层 - 多线程-GCD

iOS 底层 - 多线程-GCD

作者: 水中的蓝天 | 来源:发表于2020-04-06 18:31 被阅读0次

    本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

    队列

    这里的队列指执行任务的等待队列,即用来存放任务的队列;

    队列是一种特殊的线性表,一般开发中的队列有两种形式:串行队列、并发队列;

    GCD

    • GCD是用来替代NSTread线程技术, 可以充分利用设备的多核且保证线程安全。
    • 开发者可以通过GCD来开启子线程加入到队列中执行任务。

    GCD源码

    GCD的常用函数

    • GCD中有2个用来执行任务的函数

    用同步的方式执行任务

    void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
    

    queue:队列
    block:任务

    用异步的方式执行任务

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
    • 串行队列(Serial Dispatch Queue
      • 让任务排队执行(一个任务执行完毕后,再执行一按一个任务)
      • 采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。

    每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:

    串行队列.png
    //创建串行队列
    第一个参数:const char *_Nullable label  队列名 自定义就可以
    第二个参数:dispatch_queue_attr_t _Nullable attr  队列标识 
    DISPATCH_QUEUE_SERIAL:串行队列
    
    dispatch_queue_create("mySerialqueu", DISPATCH_QUEUE_SERIAL);
    
    

    注意:GCD创建串行队列,虽然使用了create方式创建;但这是不需要开发者自己释放的,GCD内部会进行处理。
    CFUUIDCreate()这样的CoreFoundation框架中的C语言API不同,这是需要调用CFRelease(<#CFTypeRef cf#>)手动内存管理的.

    -主队列

    • 主队列由系统在程序启动之初就自动创建完成

    • 主线程所在的队列就是主队列

    • 主队列也是一种特殊的串行队列,特殊是因为只能获取不能创建;主队列不接受创建

    • 并发队列 (Concurrent Dipatch Queue)

      • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      • 并发功能只有在异步(dispatch_async)函数下才有效

      注意:这里的同时并不是真正的多条线程同一时间做任务,而是在多条线程中快速的切换执行任务,只不过这个速度非常的快而已。

    并发队列.png
    
    //获取全局并发队列
    DISPATCH_QUEUE_PRIORITY_HIGH           高               2
    DISPATCH_QUEUE_PRIORITY_DEFAULT   默认==中               0
    DISPATCH_QUEUE_PRIORITY_LOW            底              (-2)
    DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台即:最低级别  INT16_MIN
    
    第一个参数:优先级 ,0 == DISPATCH_QUEUE_PRIORITY_DEFAULT
    第二个参数:保留供将来使用的值-flags。传递任何非零的值都可能导致-->空返回值。所以最好传0进去
    dispatch_get_global_queue(0, 0);
    
    
    //创建并发队列
    第一个参数:const char *_Nullable label  队列名 自定义就可以
    第二个参数:dispatch_queue_attr_t _Nullable attr  队列标识 
    DISPATCH_QUEUE_CONCURRENT :并发队列
    
    dispatch_queue_create("myConcuQueu", DISPATCH_QUEUE_CONCURRENT);
    
    

    容易混淆的术语

    有4个术语比较容易混淆:同步异步并发串行

    • 同步和异步主要影响:能不能开启新的线程

      • 同步:在当前线程中执行任务,不具备开启新线程的能力;当然这个当前线程包括:主线程、子线程
      • 异步:在新的线程中执行任务,具备开启新线程的能力;就是不一定都能开;比如: 在主队列中就无法开启新线程
    • 并发和串行主要影响:任务的执行方式

      • 并发:多个任务并发(同时)执行
      • 串行:一个任务执行完毕后,再执行下一个任务

    各种队列的执行效果

    队列的执行效果@2x.png
    • 补充:
      • 使用dispatch_sync函数当前串行队列添加任务,就会造成死锁(前提:当前串行队列中的任务还没有执行完);
      • 就是说判断死锁的条件是:dispatch_sync(同步)&当前串行队列&当前串行队列中的任务还没有执行完&添加任务
    死锁@2x.png
    可以看出
    • 异步并发队列 :会开启新线程并且同时执行多个任务 ,其他情况均不会并发执行任务
    • 异步串行队列:会开启一条新线程,但执行任务时一个一个执行
    • 其他组合: 全部不会开启新线程,执行任务时一个一个执行

    问题1: 以下代码是在主线程执行,输出结果是什么 ? 为什么 ?

    - (void)viewDidLoad {
    
        [super viewDidLoad];
        NSLog(@"任务1");
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"任务2");
        });
        NSLog(@"任务3");
      }
      // dispatch_sync: 立马在当前线程执行任务, 执行完毕才会继续往下执行
    

    输出结果:任务1

    原因:

    • 队列需要遵循FIFO原则,主队列是串行执行任务的;当前viewDidLoad方法就是一个任务,该任务正在执行中还没有结束; 要想从主队列里取出任务2执行,就需要等viewDidLoad执行完才可以;但是dispatch_sync是同步执行,这就意味着要立刻执行任务2,且要执行完任务2才能继续往下执行;你等我我等你,大家都在等这就造成死锁。

    把问题1中的dispatch_sync函数改为dispatch_async函数呢 ?

    输出结果:任务1、任务3、任务2
    原因:dispatch_async不要求立马在当前线程同步执行任务,可以继续执行后面的代码

    问题2: 举一个生活中的例子来说明线程死锁

    小李和小明到自动取款机取钱;小明先到于是就开始插卡取钱了,这时候小李也到了也想取钱,小李呢脾气很火爆上来就要直接往卡槽里插卡取钱;
    小明委屈的说:你让我先把卡退出来你在插卡呀。。。
    小李一脸蛮横:哼!!! 我不管我要插卡取钱 !说话就拿卡往卡槽里塞
    这时取款机老大爷怒了直接把俩人的卡全吞了 !END

    问题3 :以下代码是在主线程执行输出结果是什么 ? 为什么 ?

    - (void)interview
    {
    
        NSLog(@"执行任务1");
      //创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    
    //异步开启子线程加入到串行队列中
        dispatch_async(queue, ^{ // 整一个dispatch_async函数调用代号:block0
    
            NSLog(@"执行任务2");
            
            dispatch_sync(queue, ^{ // dispatch_sync函数调用代号:block1
                NSLog(@"执行任务3");
            });
        
            NSLog(@"执行任务4");
        });
        
        NSLog(@"执行任务5");
    
    }
    

    输出结果:执行任务1、执行任务5、执行任务2 ---->死锁在dispatch_sync函数调用

    原因:异步串行队列,串行队列是先进先出(FIFO),block0任务先被加入到队列中的,所以必须他先执行完才能执行后面加入的block1;
    此时开启的同步任务执行block1,block0要执行完就需要等待block1执行完才能执行后面代码,block1需要等待block0执行完才能够开始执行;
    这样就大家谁都没办法执行任务了 !直接阻塞了!

    如果问题3中 dispatch_sync() 函数的任务队列是在另一个队列(串行队列、并行队列)中执行;结果如何 ?

    答 :不会死锁了!因为不在同一个队列中。

    问题4:以下代码是在主线程执行输出结果是什么 ? 为什么 ?

    - (void)interview
    {
        NSLog(@"执行任务1");
        //创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
      
      //把异步任务添加到并发队列中
        dispatch_async(queue, ^{ // block0
    
            NSLog(@"执行任务2");
    
           //把同步操作任务添加到并发队列中
            dispatch_sync(queue, ^{ // block1
                NSLog(@"执行任务3");
            });
            
            NSLog(@"执行任务4");
        });
        
        NSLog(@"执行任务5");
    }
    
    

    输出结果:执行任务1、执行任务5、执行任务2、执行任务3、执行任务4

    原因: 并发队列支持同时执行多个任务,现在往并发队列中添加了两个任务:异步任务、同步任务;他们是不需要等另一个执行完再执行的,执行异步任务的同时也可以执行同步任务;但由于block0是异步所以执行到block1会先让他执行,之后再执行后面的代码。

    问题5: 手动创建的全局并发队列和直接获取的全局并发队列有什么区别 ?

    • 全局并发队列不论获取多少次,返回的都是同一个队列;即 内存地址相同
    • 开发者手动创建的全局并发队列,每次创建出来的都是一个全新的全局并发队列;同名的也依然会创建全新的队列,但不建议这么做;因为有一些处理逻辑会用到这个名字,这样显然会出错 !

    相关文章

      网友评论

          本文标题:iOS 底层 - 多线程-GCD

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