美文网首页
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