iOS 多线程之GCD

  • iOS 多线程,自旋锁和互斥锁详解
  • iOS 多线程之GCD
  • iOS 多线程之NSOperation
  • iOS 多线程之NSThread

1 GCD简述

Apple源码--Dispatch

Grand Central Dispatch(GCD)Apple开发的一个多核编程的较新的解决方法.它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统.它是一个在线程池模式的基础上执行的并发任务.在Mac OS X 10.6雪豹中首次推出,也可在iOS 4及以上版本使用.

GCD优点

  • GCD可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

2 GCD任务和队列

任务

任务: 执行操作的意思,就是说你在线程中执行的那段代码.在GCD中是放在block中的.执行任务有两种方式同步执行异步执行

  • 同步执行(sync):
    (1) 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行
    (2) 能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):
    (1) 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
    (2) 可以在新的线程中执行任务,具备开启新线程的能力
    注意: 异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程.这跟任务所指定的队列类型有关(下面会讲)

任务的创建分为:同步任务dispatch_sync和异步任务dispatch_async

dispatch_sync(queue, ^{
    // 同步执行任务
    // code snippet
});
dispatch_async(queue, ^{
    // 异步执行任务
    // code snippet
});

队列(Dispatch Queue)

队列:队列指执行任务的等待队列,即用来存放任务的队列.队列是一种特殊的线性表,采用FIFIO(先进先出)的原则.

image

GCD队列分为两种:串行队列并行队列
主要区别: 执行顺序不同,以及开启线程数不同.

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行.让任务一个接着一个地执行.(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行.(可以开启多个线程,并且同时执行任务)

注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效.其他线程下,串行执行任务

image

队列的创建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

  • 参数0: 队列的唯一标识符,队列的名称推荐使用应用程序id这种逆序全程域名
  • 参数1: 用来识别是串行队列还是并发队列 (DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT)
// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并发队列 (参数0: 填写默认 , 参数1: 填写0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 队列和同步,异步任务组合

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

4 GCD方法

4.1 dispatch_after:延时执行方法

主要:dispatch_after方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中.准确来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的

- (void)after {
    NSLog(@"当前线程%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"线程after:%@", [NSThread currentThread]);  // 打印当前线程
    });
}

4.2 dispatch_once:只执行一次

在创建单例、或者有整个程序运行过程中只执行一次的代码时,就可以使用dispatch_once方法.dispatch_once方法能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下, dispatch_once也可以保证线程安全.

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次, 默认线程安全
        // code snippet
    });
}

4.3 dispatch_barrier_async: 栅栏函数

Apple官方文档

对这个函数的调用总是在block被提交之后立即返回,并且从不等block待被调用.当barrier block到达私有并发队列的前端时,它不会立即执行.相反,队列将等待,直到当前执行的块完成执行.此时,barrier block自己执行.在barrier block之后提交的任何block都不会执行,直到barrier block完成.
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列.如果传递给此函数的队列是一个串行队列或一个全局并发队列,则此函数的行为与dispatch_async函数类似.

image
  • dispatch_barrier_sync

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });

    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
    
    NSLog(@"任务7, %@", [NSThread currentThread]);
}];
        NSLog(@"barrier任务4, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });
}

执行结果:

任务2, {number = 4, name = (null)}
任务3, {number = 6, name = (null)}
任务1, {number = 5, name = (null)}
任务4 barrier, {number = 1, name = main}
任务7, {number = 1, name = main}
任务5, {number = 5, name = (null)}
任务6, {number = 6, name = (null)}
  • dispatch_barrier_async
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务6, %@", [NSThread currentThread]);
    });

    NSLog(@"任务7, %@", [NSThread currentThread]);
}

执行结果:

任务7, {number = 1, name = main}
任务2, {number = 3, name = (null)}
任务1, {number = 6, name = (null)}
任务3, {number = 5, name = (null)}
任务4 barrier, {number = 5, name = (null)}
任务5, {number = 5, name = (null)}
任务6, {number = 6, name = (null)}

4.4 dispatch_apply:快速迭代(高效for循环)

Apple官方文档

此函数将多个调用的block提交给调度队列,并等待任务block的所有迭代完成后再返回.如果目标队列是由dispatch_get_global_queue返回的并发队列,则可以并发调用该block,因此它必须是reentrant安全的.在并发队列中使用此函数可以作为一种有效的并行for循环.
迭代的当前索引被传递给block的每次调用.

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // dispatch_apply是同步的
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
    });

    // 如果想异步,包装一层
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"异步index:%zu %@", index, [NSThread currentThread]);
        });
    });
}

4.5 dispatch_group: 队列组

  • 调用队列组的dispatch_group_async先把任务放到队列中,然后将队列放入队列组中.或者使用队列组的dispatch_group_enter、dispatch_group_leave组合来实现
  • 调用队列组的dispatch_group_notify回到指定线程执行任务.或者使用dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)
  • dispatch_group_notify: 监听group中任务的完成状态,当所有的任务都执行完成后,追加任务到group中,并执行任务
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
        NSLog(@"group任务完成");
    });
}

执行结果:

任务2, {number = 5, name = (null)}
任务1, {number = 8, name = (null)}
任务3, {number = 1, name = main}
group任务完成
  • dispatch_group_wait:暂停当前线程(阻塞当前线程),等待指定的group中的任务执行完成后,才会往下继续执行
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group任务完成");
 }

执行结果:

任务2, {number = 4, name = (null)}
任务1, {number = 6, name = (null)}
group任务完成
  • dispatch_group_enter(), dispatch_group_leave
- (void)group1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务2, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务3, %@", [NSThread currentThread]);
        NSLog(@"group任务完成");
    });
}

执行结果:

任务2, {number = 4, name = (null)}
任务1, {number = 7, name = (null)}
任务3, {number = 1, name = main}
group任务完成

4.6 dispatch_semaphore: 信号量

dispatch_semaphoreGCD中的信号量,持有计数的信号, dispatch Semaphore中,使用计数来完成这个功能,计数小于0时等待,不可通过.计数为0或大于0时可通过.

主要使用:

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁

dispatch_semaphore三个方法:

  • dispatch_semaphore_create: 创建一个semaphore并初始化信号的总量
  • dispatch_semaphore_signal: 发送一个信号,信号计数 + 1
  • dispatch_semaphore_wait: 可以使总信号量 - 1,信号总量小于0时就会一直等待(阻塞所在线程),否则就可以正常执行
- (void)semaphor {
    
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任务1, %@", [NSThread currentThread]);

        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"当前线程1:%@", [NSThread currentThread]);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任务完成");

}

执行结果:

当前线程:{number = 1, name = main}
当前线程1:{number = 1, name = main}
任务1, {number = 6, name = (null)}
任务完成

从打印结果可知执行流程为:

  • semaphore开始计数为0
  • 异步任务加入队列之后,不等待继续执行, 执行到dispatch_semaphore_wait方法, 信号量计数- 1-1小于0,当前线程进入等待状态
  • 任务1执行开始执行, 执行完成后,执行dispatch_semaphore_signal,信号量计数+ 10,阻塞线程恢复继续执行

完整代码见GitHub->多线程(附大厂面试讲解)


如有不足之处,欢迎予以指正, 如果感觉写的不错,记得给个赞呦!

你可能感兴趣的:(iOS 多线程之GCD)