运行时

运行时相关库源码解析

Posted by LML on March 23, 2020

前言

线程、队列、锁 与 Runloop iOS 中比较重要且相关联的概念。本文为此系列的第一篇,主要介绍与线程、队列相关几组概念和这几组概念之间的关系。下面为本篇介绍的提纲:

  • 队列、任务
  • 同步、异步
  • 串行队列、并行队列
  • 线程与队列的组合
  • 线程与队列的关系
  • 死锁

#1.任务、队列

  • 任务:执行什么操作,比如block
  • 队列:用来存放任务 - The main queue(主线程串行队列): 一般绝大多数情况,提交至Main queue的任务会在主线程中执行 dispatch_queue_t queue = dispatch_get_main_queue(); - Global queue(全局并发队列): 全局并发队列由整个进程共享,有高、中(默认)、低、后台四个优先级别
``` //默认
  dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  	//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  	```
  	- Custom queue (自定义队列): 可以为串行,也可以为并发。
``` dispatch_queue_t serialQueue = dispatch_queue_create(“com.xyy.serialQueue”, DISPATCH_QUEUE_SERIAL);

dispatch_queue_t conCurrentQueue = dispatch_queue_create(“com.xyy.conCurrentQueue”, DISPATCH_QUEUE_CONCURRENT);

``` - GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的前台队列,以及一个优先级更低的后台队列(用于 I/O)。 另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。自定义队列的默认 Target Queue 就是 Default Priority Queue

  • 串行队列、并行队列进一步理解

    首先明确:无论是串行队列还是并行队列,任务出队列的时候都是遵守先进先出的规则,只不过:

    • 并行队列不需要等待任务执行完在出队列,只需要等前一个task分配了线程就可以出队列;task要想出并行队列,必须要前一个分配到了线程才能出队列——若是自己定义的并行的队列,这个队列的task需要放入全局队列,那是不是需要等全局队列空了,自定义的队列里面的task才能按顺序出去——是的
    • 串行队列:等待前一个tesk执行完毕,下一个才出队列。让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务),其实是因为没有出队列

2. 同步、异步

//同步
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
//异步
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// queue:队列 block:任务
  • 同步:
    • 阻塞当前线程,且不具备开启新线程的能力,注意理解不具备开启新线程的能力 - 指的是阻塞当前线程后,不开启新的线程,但是不一定在当前线程执行 - 例:如果是非主线程同步主线程执行,会阻塞当前线程,然后task在主线程执行。——其实也没有开辟新线 - 除上述特例之外,一般都在当前线程执行 - 不开启新线程的例子2:当前在主线程,然后同步(sync)自定义的串行(并行)队列 + task,这时候不会死锁,会中断主线程,然后主线程执行task
  • 异步
    • 不阻塞当前线程
    • 可以在新的线程中执行任务,具备开启新线程的能力
    • 异步主队列不具备开启新线程的能力,只能在主线程执行
    • 开辟新的线程是系统进行的

⚠️队列与线程的关系

  • 无论dispatch_sync 还是 dispatch_async 只能影响当前的 block(即task),否则若一个队列里面的 task 即有 sync 进来的也有 async 进来的时候,如果不执行“ dispatch_sync 还是 dispatch_async只能影响当前的 block ”的规则,就会乱了
  • 一个队列可以放多种任务,比如dispatch_async进来的或者dispatch_sync`` 进来的,但是必须遵循先进先出规则
  • 一个队列不会跟一个线程绑定,可能和多个线程有关系
  • queue 是并行还是串行影响的是task满足什么条件才能出队列
  • 一个线程可以执行多个队列的tsak;一个队列的task也可以给对多个线程执行,需要看该task当时是如何加入的队列
  • 主线程不一定只执行主队列的任务
    • 主线程同步自定义队列,主线程会执行自定义队列的task

3. 线程、任务、队列与同步异步组合

同步 + 串行 (主线程+非主线程)

  • 主线程同步到主队列——卡死 - 主线程正在执行主队列的task,主线程被阻塞,主队列没有收到通知其 task 已经完成。 主队列的下个任务不会出队列,所以被同步放入主队列的 task 没法出队列。所以卡死。
  • 主线程同步到自定义串行队列 - 主线程中断——自定义串行队列中刚才的通过同步 sync 添加的 task 会有个指针执行主线程——自定义串行队列等待前面task执行完毕后——被加入到全局队列——根据task指针——到主线程执行——主线程可以执行非主队列task
  • 非主线程同步主队列
    • 阻塞当前线程,等待主线程空闲时候执行加入主队列的 task
  • 非主线程同步自定义串行队列
    • 若是自定义的队列是当前的线程执行任务所在的队列——死锁
    • 新的自定义队列:阻塞当前线程——等自定义串行队列前面 task 执行完毕——该 task 出自定义队列入——defult global queue(并行)——按顺序出defult global queue,不需要等待defult global queue内部 task 执行完毕,根据task指针找到原线程执行
  • 怎么在回到原来的线程执行呢?
    • Task 有指针

同步 + 并行 (主线程+非主线程)

因为目标是并发队列,所以目标肯定不会是在主队列了

  • 当前线程主线程,自定义的并发队列
    • 阻塞当前线程——自定义的并发队列中的task被加入到全局队列——主线程执行
  • 当前非主线程,自定义的并发队列
    • 阻塞当前线程——自定义的并发队列中的task被加入到全局队列——当前线程执行
    • 即使目前线程执行的队列是目标队列,也不会死锁,因为并行队列不会等到前面任务执行完毕,只需要出队列既可;

##异步 + 串行

  • 不阻塞当前线程,
    • 当前主线程
      • 目标队列:主队列,不开启新线程,等待主线程执行完前面的任务
      • 目标队列:非主队列,自定义串行队列,放入已有的GCD队列,开启一个新线程
    • 当前非主线程
      • 目标队列:主队列,不开启新线程,等待主线程执行完前面的任务
      • 目标队列:非主队列,自定义串行队列,放入已有的GCD队列,开启一个新线程
  • 如果当前任务加入同一个串行队列,即使异步执行,也会保证顺序
先执行任务1完成后->再执行任务2:原因加入了同一个串行队列,先进献出规则且需要前一个执行完才执行当前的
-(void)tandemPrivateQueue{
    //自定义私有串行队列
    dispatch_queue_t queue=dispatch_queue_create("com.privateQueue", NULL);

    //异步任务1加入队列中
    dispatch_async(queue, ^{
        NSLog(@"私有队列任务1");
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"私有队列任务1_i:%ld",i);
        }     
    });
    //异步任务2加入队列中
    dispatch_async(queue, ^{
        NSLog(@"私有队列任务2");
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"私有队列任务2_i:%ld",i);
        }
        
    });

##异步 + 并行

  • 不阻塞当前线程,开启多个线程,队列里面的任务也不一定按照顺序执行完,但是出队列按照顺序
  //两个任务的执行谁先谁后是不一定的:肯定是任务1先分到线程,但是不等第一个执行完第二个任务也出队列了
-(void)parallelGlobalQueue{
    //全局并行队列
    dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(globalQueue, ^{
        NSLog(@"全局队列任务1");
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"全局队列任务1_i:%ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"全局队列任务2");
        for (NSInteger i = 0; i < 10; i++) {
            NSLog(@"全局队列任务2_i:%ld",i);
            [NSThread sleepForTimeInterval:0.5];
        }
    });
  }

4. 死锁

  • 同步造成的死锁
    • 串行队列:
      • 同步添加task且task目标队列是当前正在执行的task所属的队列——死锁
      • 同步添加task且task目标队列不是当前正在执行的task所属的队列——不会死锁
    • 并行队列:
      • 无论是添加task的目标队列与当前执行的task所属的队列是不是同一个都不会死锁,因为并行队列中的task出去不需要前一个task执行完
  • 异步造成的死锁
    • 不会死锁
  • 队列不等于线程,锁死一般都是串行队列死锁导致的:刚出队列的task执行的线程被阻塞,解决阻塞的是当前队列后面的task的执行,若是并行队列就不会有问题,因为并行队列不需要等待任务执行完才出队列

5.小结

其实一共三组概念

  • 串行队列、并行队列
  • 同步执行、异步执行
  • 主线程、其他线程

搞清楚上述三组概念如何组合就弄明白了

6.参考

https://juejin.im/post/5a30fcfbf265da4321540a46

https://www.jianshu.com/p/44d84e275962