iOS 底层 多线程 GCD - AlvinSunny/OC-TheUnderlying GitHub Wiki

队列

这里的队列指执行任务的等待队列,即用来存放任务的队列,队列是一种特殊的线性表,一般开发中的队列有两种形式:串行队列、并发队列;

GCD

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

GCD源码

GCD的常用函数

  • GCD中有2个线程相关的函数

同步、异步

void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

queue:队列       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);

容易混淆的术语:同步异步并发串行

  • 同步和异步作用:用来控制是否开启新的线程

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

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

各种队列的执行效果

队列的执行效果@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: 手动创建的全局并发队列和直接获取的全局并发队列有什么区别 ?

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

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

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"任务1 %@",[NSThread currentThread]);

    dispatch_async(queue, ^{//异步串行 开启一条新线程从队列中取出任务顺序执行
        NSLog(@"任务2 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务3 %@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        NSLog(@"任务4 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务5 %@",[NSThread currentThread]);
    

输出结果13245, 2之所以在45前面是因为串行队列的原因,任务4需要串行执行 然而任务2和任务4在同一串行队列,串行队列的特点就是顺序执行任务, 虽然是在不同线程,任务2先被取出在4前面所以一定会先执行

谨记:队列里的任务可以被多条线程执行,队列跟线程并非一对一关系

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


    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"任务1 %@",[NSThread currentThread]);

    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"任务2 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务3 %@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        NSLog(@"任务4 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务5 %@",[NSThread currentThread]);
    

输出13245,原因跟第6题一样就算是休眠了5秒钟也是一样的

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



    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"任务1 %@",[NSThread currentThread]);//主线程

    dispatch_async(queue, ^{//开启新线程
        NSLog(@"任务2 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务3 %@",[NSThread currentThread]);//主线程
    
    dispatch_sync(queue, ^{//不会开新线程 在主线程
        NSLog(@"任务4 %@",[NSThread currentThread]);
    });
    
    NSLog(@"任务5 %@",[NSThread currentThread]);//主线程
    

13245、13425、13452, 任务1和任务3的顺序可以确定,452的顺序就不一定了并发队列对任务的执行顺序没要去所以或有三种情况,不过45两个任务来说相对的任务4在任务5前面