多线程-GCD

GCD(grand central dispatch)概要

什么是GCD

是异步执行任务的技术之一。它将应用程序中线程管理的代码放在系统级中实现。开发者只须要定义想执行的任务并追加到适当的Dispatch Queue中,至于具体是哪一个线程执行、如何执行该任务,开发者不需管,交由GCD来管理。编程

所以GCD优于其余异步技术的点:线程管理交由系统管理,效率更高。bash

线程

源码通过编译连接后生成二进制目标代码(CPU命令列),CPU顺序执行这些命令列。 一个CPU核执行CPU命令列是一条无分叉的路径,即为线程。当一个线程被挂起时,当前CPU的寄存器等信息会保存到各自路径专用的内存块中(保存上下文),线程恢复继续执行,则从内存块中取出数据,复原CPU寄存器等信息,继续执行切换路径的CPU命令列(上下文切换)。多线程

多线程编程容易发生的问题:并发

多线程的优势:app

GCD的API

总览图: 异步

Dispatch Queue

Dispatch Queue:任务队列,存储等待执行的任务。它会按照追加顺序(FIFO)执行任务async

队列种类

  1. 串行队列(Serial Dispatch Queue):其余任务要等待如今执行中的任务处理结束(一个队列对应1个线程(线程可变),即同时只能执行一个任务),有序。
  2. 并行队列(Concurrent Dispatch Queue):其余任务不需等待如今执行中的任务处理结束(一个队列对应>=1个线程,具体多少要看当前的系统资源,即同时可执行多个任务),无序。

串行队列缺点:每一个串行队列对应一个线程,当同时建立多个串行队列时,对应多个线程。此时可能会发生数据竞争。 而一个并行队列虽然同时会有多个线程,可是无论生成多少线程,都会有XNU内核来管理,因此不需担忧串行队列的问题。函数

获取队列的方式

  1. 本身建立:dispatch_queue_create
// DISPATCH_QUEUE_SERIAL/NULL/0 串行队列
    // DISPATCH_QUEUE_CONCURRENT    并行队列
    // ARC状况不须要手动释放
    dispatch_queue_t queue = dispatch_queue_create("queueIdentifier", 0);
    dispatch_async(queue, ^{
        NSLog(@"执行任务");
    });
复制代码

  1. 获取系统提供的队列

主队列:oop

dispatch_queue_t mainQueue = dispatch_get_main_queue();
复制代码

主队列是在main()被调用前,自动被系统建立的串行队列,和主线程相关联。(但这并不表明主队列中的任务只能被主线程执行)。ui

若是想要(其余线程)执行主队列中的任务,只能用如下三种方式中的一种:

  • 调用dispatch_main:叫停主线程,等待主队列中的任务执行(其余线程),永远不会返回
  • 调用UIApplicationMain (iOS) or NSApplicationMain (macOS):建立应用、应用代理、和事件循环,永远不会返回
  • 在主线程上使用一个CFRunLoopRef

全局并行队列:Global Dispatch Queue

/*
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
复制代码

注意:使用函数本身建立的队列的优先级和globalQueue中默认优先级是同等水平的。若是想要改变队列的优先级须要使用函数dispatch_set_target_queue。

变动手动建立的队列的优先级

dispatch_set_target_queue(要改变的对象obj, 目标队列tq)
复制代码

给一个队列设置目标队列会改变这个队列的某些行为:

  • 分发队列
    • 队列自己未设置优先级,则它的优先级会继承目标队列的优先级;
    • 目标队列决定了obj的销毁函数在哪一个队列上被执行;
    • 一般不会对已经加入的任务形成影响;
    • obj会持有tq;
    • obj若是是globalQ、mainQ,无效;
  • 分发资源
  • 分发I/O通道

注意:设置目标队列是,不要造成继承循环。

  1. 目标队列为串行队列:
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
    dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
    dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
    
    dispatch_async(serialQ, ^{
        NSLog(@"serialQ_1---------->");
    });
    dispatch_async(sQ3, ^{
        NSLog(@"serialQ_3----------->");
    });
    dispatch_async(sQ2, ^{
        NSLog(@"serialQ_2----------->");
    });
    此时结果是不定的,由于三个serialQueue是并行的。
复制代码
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
    dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
    dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
    
    dispatch_set_target_queue(serialQ, globalQ);
    dispatch_set_target_queue(sQ2, serialQ);
    dispatch_set_target_queue(sQ3, serialQ);
    
    dispatch_async(serialQ, ^{
        NSLog(@"serialQ_1---------->");
    });
    dispatch_async(sQ3, ^{
        NSLog(@"serialQ_3----------->");
    });
    dispatch_async(sQ2, ^{
        NSLog(@"serialQ_2----------->");
    });
    此时结果是肯定的 一、三、2。而且使用的是同一个线程
复制代码

正常状况下:不管是串行仍是并发队列,只要设置了目标队列,以后任务都会向上传到它的上一级目标队列中。所以任务其实最后都传到根上去了。因此如今这条链上的队列应该都是公用一个线程池的。所以若是要获取当前队列,即获取执行当前代码所关联的队列,结果是不定的。

GCD线程池中,系统最多会建立64个线程。手动建立的线程系统会默认设置目标队列:全局默认优先级队列。
即便是加入到并发队列中的任务:

  1. 目标队列是并行队列:

定时追加任务--dispatch_after()

设置必定时间后,将任务异步添加到队列中(而不是将任务加到队列中,必定时间后执行),所以时间可能不够准确。

函数直接返回。

  1. 相对时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"执行任务");
    });
复制代码
  1. 绝对时间 设置在某一具体时间(2011/3/25-11:00:03)时,将任务异步添加到队列中。
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second); // 取浮点数的小数部分和整数部分
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC; // 小数部分转纳秒
    milestone = dispatch_walltime(&time, 0);
    
    return milestone;
}
复制代码

Dispatch Group

当但愿知道加入到队列中的任务什么时候所有执行完成时,有两种方式:1. 将任务加入到串行队列,结束的任务加到最后便可。2. 任务加到多个队列中或者加到并行队列中,须要用到group。

  1. notify

代码解析:

// 将任务block异步加到队列中,而且将block关联到group群组中,即block持有group
    dispatch_group_async(group, globalQ, ^{
        NSLog(@"global_blk0---->%@",[NSThread currentThread]);
    });
    // 当持有group的全部任务都执行完成后,会将任务加入到队列中去执行。若是group中无任务,则当即将任务加到指定队列中
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done---->%@",[NSThread currentThread]);
    });
复制代码
  1. wait

dispatch_barrier_async

访问数据时,容易出现数据竞争的问题。 写操做不容许其余读写操做并行执行,读操做可与其余读操做并行执行。

须要dispatch_barrier_async为须要单独执行的操做加入栅栏。

代码:

同步执行--dispatch_sync()

相似于前面的dispatch_wait,线程执行到该函数,会进入等待状态,直到同步的任务结束,才会返回,继续执行。

容易形成的问题:死锁

// 线程进入等待状态,直到barrierBlock执行完成才会返回
dispatch_barrier_sync(serialQ, ^{
});
复制代码

dispatch_apply

dispatch_apply函数结合了dispatch_sync和dispatch group的功能。该函数按照指定次数将任务block添加到队列中,并等待所有任务执行完成,再返回。

并发队列:

串行队列:

应用:(不关心数据顺序时)

dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(global, ^{
        dispatch_apply([array count], global, ^(size_t i) {
            NSLog(@"%@", array[i]);
        });
    });
复制代码

暂停和恢复队列中的任务

  1. dispatch_suspend(dispatch_object_t): 挂起指定队列,已经执行的任务不受影响,还没有执行的中止执行;
  2. dispatch_resume(dispatch_object_t): 恢复指定队列,继续执行队列中的任务。

注意:

① 1,2必须是一对一的,由于执行1,dispatch_object_t的suspension count会+1;执行2会减一。当count>0时,会一直处于挂起状态。

② 当要暂停一个队列时,上述函数仅对本身手动建立的队列有效,对于系统队列(global、main)无效的。

  1. iOS 8以后,dispatch_block_cancel(dispatch_block_t)能够取消任务,可是仅能取消等待执行的任务,不能取消正在执行的任务。

信号量-- dispatch semaphore

思想相似停车场:初始有n个车位,每来一辆车,先查看n是否大于0,若是n>0,车辆进入使用车位,n--。每辆车离开停车场n++。 当初始值设置为1时,能够做为锁来使用。

保证任务只执行一次--dispatch_once

dispatch_once函数保证在整个应用程序执行中,只执行一次指定任务,即便在多线程环境下。

应用:单例模式

static NSObject *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[NSObject alloc] init];
    });
    return instance;
复制代码

Dispatch I/O

读取较大文件时,可将其分割成合适大小并发读取,提升读取速度。

相关文章
相关标签/搜索