iOS底层学习 - 多线程之GCD初探

经过上一个章节,咱们已经了解了一些多线程的基本知识。本章就来说解在平时开发中,最为经常使用的GCD的一些知识git

系列文章传送门:程序员

iOS底层学习 - 多线程之基础原理篇github

GCD简介

定义

GCD全程为Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,CGD会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只须要告诉GCD须要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。swift

优点

  • GCD 是苹果公司为多核的并行运算提出的解决方案
  • GCD 会自动利用更多的CPU内核(好比双核、四核)
  • GCD 会自动管理线程的生命周期(建立线程、调度任务、销毁线程)
  • 程序员只须要告诉 GCD 想要执行什么任务,不须要编写任何线程管理代码

GCD使用

GCD的使用API较为简单,主要分为同步和异步执行。在上一章咱们对概念已经有所了解,就不作赘述了。多线程

同步执行API并发

//queue:队列 block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
复制代码

异步执行APIapp

//queue:队列 block:任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
复制代码

咱们发如今使用GCD时,须要传入两个参数。分别是dispatch_queue_t表明添加的队列类型,dispatch_block_t为须要执行的任务,下面咱们来看一下不一样参数的运行效果。异步

dispatch_block_t任务

经过查看其定义可得,该参数其实就是一个没有参数的block回调,用来执行任务的。async

typedef void (^dispatch_block_t)(void);
复制代码

dispatch_queue_t队列

dispatch_queue_t参数表示队列的类型。根据上一章节咱们知道,队列(FIFO)在iOS中主要一下分为4种:ide

  1. 主队列(main_queue):由系统建立的串行队列。
    • 获取方式:dispatch_get_main_queue()
  2. 全局队列(global_queue):由系统建立的并发队列
    • 获取方式:dispatch_get_global_queue(long identifier, unsigned long flags);
  3. 串行队列(Serial Dispatch Queue):自定义的串行队列
    • 获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_SERIAL)
  4. 并发队列(Concurrent Dispatch Queue):自定义的并发队列
    • 获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_CONCURRENT);

其中,全局队列根据入参的不一样,会获取到不一样的队列,在平常的开发中,咱们通常入参0,来获取到主并发队列。

自定义的队列都是调用dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)方法来进行建立,两个参数分别为自定义队列名称队列类型。其中串行队列DISPATCH_QUEUE_SERIAL为宏定义的NULL,因此传NULL也表示为串行队列。

如今,咱们来看这2种执行模式,3种队列是相互搭配是什么效果

同步 +(主队列或者自定义串行队列)

相关代码以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任务2",[NSThread currentThread]);
    });
    NSLog(@"任务3:%@",[NSThread currentThread]);
}
复制代码

运行结果

打印出任务1后,程序死锁崩溃

为何会形成以上的结果呢?

首先分析一下代码:主线程执行完任务1后,在主队列dispatch_get_main_queue()中同步执行(dispatch_sync)任务2,而后执行任务3。

队列的特色是FIFO,主队列中已经存在任务viewDidLoad,往主队列加入任务2,就须要执行完viewDidLoad才能执行任务2。可是想要执行完viewDidLoad又必须先执行viewDidLoad内的任务2和任务3。这就形成了死锁

异步 +(主队列或者自定义串行队列)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"任务2:%@",[NSThread currentThread]);
    });
    NSLog(@"任务3:%@",[NSThread currentThread]);
}
复制代码

运行结果

咱们能够看到当获取到主队列后,使用异步执行的方式,不会形成程序的崩溃。

由于主线程执行任务1以后,须要异步(dispatch_async)执行任务2;而dispatch_async不要求立马在当前线程同步执行任务,也就是不会堵塞;因此主线程接着执行任务3,最后异步执行任务2。

同步+(全局队列或者自定义并发队列)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任务一:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任务二:%@",[NSThread currentThread]);
        }
    });
}
复制代码

运行结果

咱们在全局队列中同步执行任务1和任务2,经过打印能够看出,同步执行是 按顺序执行任务,执行完任务1再执行任务2。而且打印当前线程,同步执行是在 主线程中执行任务,没有开启新线程。且因为队列是并发的,并不会阻塞主线程。

异步+(全局队列或者自定义并发队列)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任务一:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@"任务二:%@",[NSThread currentThread]);
        }
    });
}
复制代码

运行结果:

能够看出任务1和任务2交错执行,并不是同步执行那样执行完任务1再执行任务2。并且经过线程编号能够看出,的确开启了新的线程。说明异步执行具有开启新线程的能力。

可是经过上面异步+主队列的打印结果咱们能够发现,在主队列时,异步执行也并无开启新的线程,而仍然是同一个主线程。说明若是在主队列中异步执行,是不会开启新线程的

缘由是由于主队列是串行队列,必须执行完一个任务再执行另外一个任务,异步执行只是没有形成堵塞,可是在主队列仍是是要一步步走的。

同一串行队列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ 
        NSLog(@"任务2");
        dispatch_sync(queue, ^{ 
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}
复制代码

运行结果

任务1,任务5,任务2,而后程序崩溃

这里形成死锁崩溃的缘由和上面同步+串行队列的缘由同样。因为主队列是串行的,因此代码必然是从上到下依次执行的。

打印完任务1后,执行dispatch_async为异步,不会阻塞线程,可是会加入自定义的串行队列中,因此会执行任务5,接着执行异步代码块中逻辑,打印任务2,接着执行dispatch_sync代码块,可是因为其是同步,串行队列中已经有了dispatch_async任务未执行完毕,此时dispatch_sync阻塞当前线程等待任务3执行,形成了相互等待,因此就死锁崩溃了

不一样串行队列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue2, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}
复制代码

运行结果

任务1,任务5,任务2,任务3,任务4

经过运行咱们能够发现,当嵌套执行时,同步异步在不一样的串行队列执行,并不会形成死锁崩溃。而是按照串行的顺序,执行代码。这事由于dispatch_sync阻塞的并非当前的线程,而是其余的线程,因此不会形成死锁等待。

同一并发队列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue, ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}
复制代码

运行结果:

任务1,任务5,任务2,任务3,任务4

主线程执行任务1以后,须要异步dispatch_async执行任务2;因此先执行主线程的任务5,而后执行任务2;接着须要在并发队列中同步dispatch_sync执行任务3,因此会形成阻塞,等待其执行完成,而后执行并发队列中的任务4。

不一样并发队列嵌套

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务2");
        dispatch_sync(queue2, ^{ 
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务5");
}
复制代码

运行结果:

任务1,任务5,任务2,任务3,任务4

这个代码的分析和不一样串行队列嵌套的相似,建立了新的队列,因此是不会形成当前队列的阻塞的。

总结

  • 同步执行没有开辟线程能力,且代码顺序执行,会产生堵塞
  • 同步执行在同一串行队列时,会形成死锁等待
  • 异步执行具备开辟线程的能力,在并发队列执行时,执行顺序不肯定,在串行队列执行时,按照顺序执行,且主队列时不会开辟新线程

参考资料

苹果多线程开源代码

iOS底层原理探索—多线程的本质

相关文章
相关标签/搜索