关于GCD中的队列、线程、任务之间的关系及死锁的理解

1. 任务与队列之间的关系

  • 任务产生后被加入到某个队列中,队列仅保管任务,不执行任务;
  • 队列分为串行队列和并行队列两种,无论是串行队列还是并行队列都遵守先进先出的原则;
  • 对于串行队列,要派发位于队首的任务,必须等待上一个派出的任务被执行完毕;即从串行队列派出的任务,同一时刻,最多只有一个任务被执行;
  • 对于并行队列,要派发位于队首的任务,无需等待上一个派出的任务被执行完毕;即从并行队列派出的任务,同一时刻,可以有多个任务被执行;

2. 任务与线程之间的关系

  • 任务由线程执行,同一时刻,一个线程只能执行一个任务;要执行下一个任务,上一个任务必须执行完成(或被暂停、取消);
  • 任务分为同步执行任务和异步执行任务两种;
  • 对于同步执行任务A,产生任务A的线程将任务交给队列后,会暂停执行自己正在执行的任务B,一直等待任务A被执行完成后,自己才会继续执行任务B;
  • 对于异步执行任务C,产生任务C的线程将任务交给队列后,不会暂停执行自己正在执行的任务B,不会等待任务C被执行完成,自己会不间断的执行任务B;

3. 队列与线程之间的关系

  • 串行队列中的所有任务只会有一个在被执行,因此同一时刻,串行队列最多只会对应一个线程;
  • 并行队列中的任务可能有多个同时在被执行,因此同一时刻,并行队列可能会对应多个线程;

4. 队列、任务、线程之间的关系

  • 串行队列中的同步任务A,会有产生任务A的线程T执行,因为线程T在产生任务A后即暂停正在执行的任务B,任务A被队列派出后需要有一个线程执行,而此时线程T一定处于空闲状态,与其新开一个线程,不如利用该处于空闲的线程T。即任务A由该线程T在暂停执行B的时间段内执行,然而该线程T并不一定是暂停B后立即执行A,要等待A所在的队列将A派发出才能开始执行,这个队列会等待前一个派发出的任务执行完成之后才会派发A,因此线程T在执行任务A之前可能会等待一段时间;
  • 串行队列中的异步任务A,由于产生任务A的线程T1不会等待A被执行完成,因此A被队列派发出后,线程T1不一定为空,因此A会被交给一个新的线程T2去执行,该线程T2可能会执行该串行队列派出的下一个异步任务B,但不一定,特别是异步任务A和异步任务B之间有一个同步任务C的情况下,C不由线程T2执行,C被执行的这段时间内线程T2可能会接到新任务,派发出异步任务B时T2正在执行任务,任务B就会被派发到其他线程;
  • 并行队列中的同步任务A与在串行队列中类似,也有产生任务A的线程T执行,不同的是,该任务A可能会被所在的队列立即派出,因为并行队列派出新任务无需等待之前派出的任务执行完成,但是,若该队列中有很多任务还未派出,任务A也要等待从队尾移动到队首之后才会被派出;
  • 并行队列中的异步任务A,异步任务B,异步任务C一般会被交由不同的新线程执行,因为队列派出任务B时,产生任务A的线程和执行任务A的线程都不一定为空闲状态,而任务B可以被立即执行,因此只要系统还可以产生新线程,就会产生一个新线程来执行任务B,当然系统产生新线程的能力有限,若任务过多,不能产生足够多的新线程来执行任务,那么未派出的任务也会等待有空闲线程时才会被派出。

5. 主队列和主线程

  • 主队列是串行队列;
  • 与普通串行队列不同的是,主队列中的所有任务都由主线程执行,将异步任务加入主队列也不会开启新线程;

6. 常见的死锁

直接上代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t serialQueue = dispatch_queue_create("jason_serial_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"1");
        //串行队列同步任务中嵌套同步任务会形成死锁
        dispatch_sync(serialQueue, ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    });

    dispatch_async(serialQueue, ^{
        NSLog(@"4");
        //串行队列异步任务中嵌套同步任务会形成死锁
        dispatch_sync(serialQueue, ^{
            NSLog(@"5");
        });
        NSLog(@"6");
    });

    //主队列正在执行的任务中嵌套同步任务会形成死锁
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"sync at mainQueue");
    });
 }

一句话总结,在串行队列中的任务中嵌套同步任务。

原因分析:
  • 内层的同步任务要等待所在串行队列上次派发出的任务执行完成才能被派发;
  • 所在串行队列上次派发出的任务就是正在执行的外层任务;
  • 正在执行的外层任务已暂停等待内层任务执行完成;
  • 由于外层任务未完成,内层任务不会被派发;
  • 内层任务不会被派发就不能被执行完成,不能执行完成外层任务就不能继续,死锁产生。
  • 主队列是串行队列,主线程正在执行的任务是串行队列中的任务,故在向主队列中加入同步任务会形成死锁。要想在主线程中进行同步任务,请将其加入到其他队列。

7. 简单测试

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t serialQueue = dispatch_queue_create("jason_serial_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue1");
        sleep(2);
        NSLog(@"serialQueue2");
        sleep(2);
        NSLog(@"serialQueue3");
        NSLog(@"thread1:%@", [NSThread currentThread]);
    });
    NSLog(@"mainQueue1");
    
    dispatch_async(serialQueue, ^{
        NSLog(@"serialQueue4");
        NSLog(@"serialQueue5");
        NSLog(@"serialQueue6");
        NSLog(@"thread2:%@", [NSThread currentThread]);
    });
    NSLog(@"mainQueue2");
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"serialQueue7");
        sleep(2);
        NSLog(@"serialQueue8");
        sleep(2);
        NSLog(@"serialQueue9");
        NSLog(@"thread3:%@", [NSThread currentThread]);
    });
    NSLog(@"mainQueue3");

    NSLog(@"thread4:%@", [NSThread currentThread]);
}
问题:
  1. mainQueue1、mainQueue2、mainQueue3三者的输出顺序是是否确定?
  2. serialQueue4会不会出现在serialQueue1、serialQueue2、serialQueue3中间?
  3. serialQueue1、serialQueue2一直到serialQueue9的输出顺序是否确定?
  4. mainQueue1可能出现在serialQueue1、serialQueue2一直到serialQueue9中间的哪些位置?
  5. thread1、thread2、thread3、thread4之间有什么关系?
答案:
  1. 一定按照mainQueue1,mainQueue2,mainQueue3的顺序输出,这个就不用解释了。
  2. 不会。因为对于串行队列,上一个派发出的任务完成之前不会派发新的任务,serialQueue3输出之前,serialQueue4所在任务会持续在serialQueue内等待。
  3. 一定按照serialQueue1、serialQueue2、serialQueue3、serialQueue4、serialQueue5、serialQueue6、serialQueue7、serialQueue8、serialQueue9的顺序输出。
  4. mainQueue1可能出现在serialQueue4之前的任何位置,但由于有个sleep(2),正常情况下只会出现在serialQueue2之前,serialQueue1前后的两个位置。
  5. thread1、thread2都不是主线程,(由于两个异步任务在等待前一个异步任务执行完成,所以很可能为同一线程;若前一个异步任务结束与后一个异步任务开始的时间间隔较大,比如中间还有一个同步任务,或前一个异步任务执行结束时后一个异步任务还未入队,两个线程为同一线程的可能性就没那么大了);thread3、thread4为主线程。

你可能感兴趣的:(关于GCD中的队列、线程、任务之间的关系及死锁的理解)