GCD(一)

说说队列和任务。
在此之前先说说几个基本概念:串行,并行,同步,异步。

串行:在同一时间,队列里有且只有一个任务被执行,即一个任务执行完毕之后再执行下一个任务。

并行:在同一时间,有多个任务被执行。

同步:在当前线程中执行任务,不具备开启新线程的能力,

异步:在新的线程中执行任务,具备开启新线程的能力,是多线程的代名词,一般用来执行较耗时的任务;

1.简述队列和任务

队列的特点是FIFO(先进先出),排在前面的任务先执行;

队列分为串行和并行,任务分为同步和异步;

队列只负责任务的调度,不负责任务的执行;

任务是在线程中执行的;

2.简述几种常见的队列

串行队列:队列中的任务只会顺序执行。任务只会按照顺序被调度,前一个任务不执行完毕,队列就不会调度。

dispatch_queue_t queue = dispatch_queue_create("com.xxx.xxx", DISPATCH_QUEUE_SERIAL);

并行队列:队列中的任务通常会并发执行。只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是否有任务在执行,只要有线程可以利用,队列就会调度任务。

dispatch_queue_t queue = dispatch_queue_create("com.xxx.xxx", DISPATCH_QUEUE_CONCURRENT);

主队列:专门用来在主线程调度任务的队列,所以主队列的任务都要在主线程来执行,主队列会随着程序的启动一起创建,每一个应用程序都对应唯一一个主队列,直接使用即可,在多线程开发中,使用主队列更新UI。

dispatch_queue_t queue = dispatch_get_main_queue();

全局队列:系统为了方便程序员开发提供的,直接拿过来用就可以。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

问:那么全局队列跟并行队列的区别是什么呢?

答:全局队列无论在ARC还是MRC下都不需考虑释放,因为是系统提供的,所以我们只需拿过来用就可以了,其工作表现与并行队列一致,但在调试时,无法确认操作所在队列;而并行队列在MRC下,创建出来后,需手动释放,dispatch_release()。

主队列是GCD自带的串行队列,会在主线程中执行。异步全局并发队列 开启新线程,并发执行。

串行队列开启异步任务,是有顺序的。

并行队列里开启同步任务是有执行顺序的,只有异步才没有顺序。

串行队列开启异步任务后嵌套同步任务造成死锁。

3.队列+任务 有四种组合,分别是:

串行队列同步执行
串行队列异步执行
并行队列同步执行
并行队列异步执行

下面给出几种组合的代码和输出结果:

串行同步

dispatch_queue_t queue_serial = dispatch_queue_create("com.xiaohui.serial", DISPATCH_QUEUE_SERIAL);

dispatch_sync(queue_serial, ^{
    NSLog(@"block0");
});

dispatch_sync(queue_serial, ^{
    NSLog(@"block1");
});

dispatch_sync(queue_serial, ^{
    NSLog(@"block2");
});

dispatch_sync(queue_serial, ^{
    NSLog(@"block3");
});

dispatch_sync(queue_serial, ^{
    NSLog(@"block4");
});

NSLog(@"done");

串行异步

dispatch_queue_t queue_serial = dispatch_queue_create("com.xiaohui.serial", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue_serial, ^{
    NSLog(@"block0");
});

dispatch_async(queue_serial, ^{
    NSLog(@"block1");
});

dispatch_async(queue_serial, ^{
    NSLog(@"block2");
});

dispatch_async(queue_serial, ^{
    NSLog(@"block3");
});

dispatch_async(queue_serial, ^{
    NSLog(@"block4");
});

NSLog(@"done");

并行同步

dispatch_queue_t queue_concurrent = dispatch_queue_create("com.xiaohui.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue_concurrent, ^{
    NSLog(@"block0");
});

dispatch_sync(queue_concurrent, ^{
    NSLog(@"block1");
});

dispatch_sync(queue_concurrent, ^{
    NSLog(@"block2");
});

dispatch_sync(queue_concurrent, ^{
    NSLog(@"block3");
});

dispatch_sync(queue_concurrent, ^{
    NSLog(@"block4");
});

NSLog(@"done");

并行异步

dispatch_queue_t queue_concurrent = dispatch_queue_create("com.xiaohui.concurrent", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue_concurrent, ^{
    NSLog(@"block0");
});

dispatch_async(queue_concurrent, ^{
    NSLog(@"block1");
});

dispatch_async(queue_concurrent, ^{
    NSLog(@"block2");
});

dispatch_async(queue_concurrent, ^{
    NSLog(@"block3");
});

dispatch_async(queue_concurrent, ^{
    NSLog(@"block4");
});

NSLog(@"done");

所以:只有异步才可开启新的线程,并行+异步才是真正意义上的并发,可新开多个线程处理任务,任务并发执行(不按顺序执行);

4.GCD实现原理分析

GCD有一个底层线程池,池中放的是一个个的线程。之所以称为“池”,是因为它里面的线程是可以重用的,当一段时间后某个线程没有被调用的话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的,池是系统自动维护的,不需要我们管。

我们需要关心什么呢?我们只需关心向队列中添加任务,队列调度即可。

如果队列中放的是同步任务,当任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池,这样队列中的任务反复调度,因为是同步的,所以当我们用[NSThread currentThread]打印的时候,其实是同一条线程。

如果队列中放的是异步任务,当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。

这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,从而节约系统的开销,提高效率。在iOS7.0的时候,使用GCD系统通常只能开5~8条线程,而在iOS8.0以后,系统可以开启很多条线程,但在实际开发应用中,建议开启线程3~5条最为合理。