GCD日记

更新文章:
死锁 一般都是由于产生了资源竞争,而 GCD 死锁 的充分条件是:

在串行队列的任务执行过程中,再次向它派发同步任务。

当我们同步的提交一个任务时,首先会阻塞当前队列,然后等到下一次 runloop 时再在合适的线程中执行 block。

下面的代码:

dispatch_sync(dispatch_get_main_queue(), ^{});

主队列环境下等同于:

//这里是sync或async都不影响结果
dispatch_sync(dispatch_get_main_queue(), ^{//任务1
    //任务1的前半部分
    dispatch_sync(dispatch_get_main_queue(), ^{//任务2
    });
    //任务1的下半部分
});

即串行队列的任务1执行过程中,向它派发了一个同步任务2,导致两个任务产生竞争。

队列线程 之间并没有 拥有关系(ownership);
主队列环境=>主线程,主线程环境≠>主队列

一、基础概念

GCD

GCD 是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:
1.GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
2.GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
3.GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。

Serial vs. Concurrent 串行 vs. 并发
//串行
DISPATCH_QUEUE_SERIAL
//并发
DISPATCH_QUEUE_CONCURRENT
Synchronous vs. Asynchronous 同步 vs. 异步
//同步执行
dispatch_sync(..., ^(block))
//异步执行
dispatch_async(..., ^(block))
Critical Section 临界区

就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它很可能会变质(译者注:它的值不再可信)。

Race Condition 竞态条件

这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。

Deadlock 死锁

两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。

Thread Safe 线程安全

线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。

Context Switch 上下文切换

一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。

Concurrency vs Parallelism 并发与并行

并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉。虽然你可以编写代码在 GCD 下并发执行,但 GCD 会决定有多少并行的需求。并行要求并发,但并发并不能保证并行。更深入的观点是并发实际上是关于构造。当你在脑海中用 GCD 编写代码,你组织你的代码来暴露能同时运行的多个工作片段,以及不能同时运行的那些。如果你想深入此主题,看看 this excellent talk by Rob Pike 。

二、dispatch各知识点与应用场景

先上dispatch头文件分布图:


GCD日记_第1张图片
dispatch头文件
① dispatch block

dispatch_block_t是GCD队列专用block类型,没有参数,没有返回值。

简单定义无参数回调函数:

@property (nonatomic,copy) dispatch_block_t blockAction;

使用方式和一般的无参数回调函数相同

② dispatch IO与dispatch data

文件处理接触较少,mark:
星光社的戴铭--细说GCD(Grand Central Dispatch)如何用

③ dispatch queue

GCD 提供有 dispatch queues 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。

所有的调度队列(dispatch queues)自身都是线程安全的,你能从多个线程并行的访问它们。 GCD 的优点是显而易见的,即当你了解了调度队列如何为你自己代码的不同部分提供线程安全。关于这一点的关键是选择正确类型的调度队列和正确的调度函数来提交你的工作。

Serial Queues 串行队列 与 Concurrent Queues 并发队列
//串行队列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行队列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
Queue Types 队列类型
  • dispatch_get_main_queue
    首先,系统提供给你一个叫做主队列的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • dispatch_get_global_queue
    系统同时提供给你好几个并发队列。它们叫做全局调度队列 。目前的四个全局队列有着不同的优先级:** DISPATCH_QUEUE_PRIORITY_BACKGROUND DISPATCH_QUEUE_PRIORITY_LOW DISPATCH_QUEUE_PRIORITY_DEFAULT** 以及 ** DISPATCH_QUEUE_PRIORITY_HIGH**。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
// DISPATCH_QUEUE_PRIORITY_HIGH(QOS_CLASS_USER_INTERACTIVE)
// DISPATCH_QUEUE_PRIORITY_DEFAULT(QOS_CLASS_USER_INITIATED)
// DISPATCH_QUEUE_PRIORITY_LOW(QOS_CLASS_UTILITY)
// DISPATCH_QUEUE_PRIORITY_BACKGROUND(QOS_CLASS_BACKGROUND)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)

QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

  • 自定义队列
    最后,你也可以创建自己的串行队列或并发队列。
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL);

也可以设置自定义队列的优先级:

//dipatch_queue_attr_make_with_qos_class IOS8.0之后开放
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", attr);
//更多时候使用dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL); //需要设置优先级的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级
dispatch_set_target_queue(queue, referQueue); //设置queue的优先级和referQueue的一样

dispatch_set_target_queue也可以设置队列层级体系,设置dispatch_set_target_queue(queue, referQueue); 也相当于把queue的任务对象指派到referQueue中执行。比如说将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。这部分知识以后用到再进行扩展

dispatch_after
//延迟3秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // do you something
    });

dispatch_after 工作起来就像一个延迟版的 dispatch_async 。你不能控制实际的执行时间,且一旦 dispatch_after 返回也就不能再取消它。
dispatch_after最佳使用环境:主队列(串行)

dispatch_barrier

通过下面这个图了解一下dispatch_barrier障碍函数


GCD日记_第2张图片

注意到正常部分的操作就如同一个正常的并发队列。但当障碍函数执行时,它是唯一在执行的事件。在障碍完成后,队列回到一个正常并发队列的样子。

    //这个例子中3和4会等待1和2执行完成后再执行
    dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"-------------------1");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"I am a barrier");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------3");
    });

    dispatch_async(queue, ^{
        NSLog(@"-------------------4");
    });

dispatch_barrier有dispatch_barrier_async和dispatch_barrier_sync两种,用法与dispatch_async和dispatch_sync一样,顾名思义sync会等待block的内容执行完再继续往下执行,而async则不会等待。
dispatch_after最佳使用环境:自定义队列(并发)

dispatch_apply

一句话阐述:并发地发起for循环的迭代,提高循环运行效率。
不过这个函数本身是同步的,会等待结构体里的内容执行完再返回。

    dispatch_queue_t concurrentQueue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, concurrentQueue, ^(size_t i) {
        NSLog(@"%zu",i);
    });
    NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞主线程的。这个log打印会在dispatch_apply都结束后才开始执行

dispatch_after最佳使用环境:并发队列

④ dispatch groups

即调度组。可以对任务进行监听,这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。如果要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。
以下代码介绍dispatch_group_t的创建与执行方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建dispatch_group_t方法
dispatch_group_t downloadGroup = dispatch_group_create();
//代表group一个任务执行过程
dispatch_group_enter(downloadGroup);
NSLog(@"-------------------");
dispatch_group_leave(downloadGroup);
//等价于dispatch_group_enter+dispatch_group_leave
dispatch_group_async(group, queue, ^{
    //do something
    NSLog(@"-------------------");
});

当组中所有的事件都完成时,GCD 的 API 以下提供了两种通知方式。

  • dispatch_group_wait
//异步调用全局调度队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1 
      //此时group在全局调度队列中
      dispatch_group_t downloadGroup = dispatch_group_create(); // 2 
 
        for (NSInteger i = 0; i < 3; i++) {
            dispatch_group_enter(downloadGroup); // 3 
            self.block = ^(){
                NSLog(@"-------------------");
                dispatch_group_leave(downloadGroup); // 4 
            };
        } 
        dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5 
        dispatch_async(dispatch_get_main_queue(), ^{ // 6 
          NSLog(@"-------------------");
        }); 
    }); 
  • dispatch_group_notify
//此时group在主队列(串行)中
  dispatch_group_t downloadGroup = dispatch_group_create(); // 1 

  for (NSInteger i = 0; i < 3; i++) {
      dispatch_group_enter(downloadGroup); // 2 
      self.block = ^(){
          NSLog(@"-------------------");
          dispatch_group_leave(downloadGroup); // 3 
      };
  }  
  dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4 
      NSLog(@"-------------------");
  }); 

需要注意的是dispatch_group_wait会阻塞当前线程,而dispatch_group_notify不会。

关于何时以及怎样使用有着不同的队列类型的 Dispatch Group :

  1. 自定义串行队列:它很适合当一组任务完成时发出通知。
  2. 主队列(串行):它也很适合这样的情况。但如果你要同步地等待所有工作地完成,那你就不应该使用它,因为你不能阻塞主线程。然而,如果需要在几个较长任务(例如网络调用)完成后更新 UI 的话,dispatch_group_notify是非常适合的方式。
  3. 并发队列:它也很适合 Dispatch Group 和完成时通知。
⑤dispatch once

dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例

+ (UIColor *)boringColor;
{
     static UIColor *color;
     //只运行一次
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
     });
     return color;
}
⑥dispatch semaphore

信号量是用来保证关键代码段不被所设定次数以上并发调用的手段。在进入一个关键代码段之前,线程必须获取一个信号量,同时最多只能有设定次数个线程可以访问临界区。其他想使用资源的线程必须在一个FIFO队列里等待。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    NSMutableArray *array = [NSMutableArrayarray];

    for (int index = 0; index < 100000; index++) {

        dispatch_async(queue, ^(){

            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//

            NSLog(@"addd :%d", index);

            [array addObject:[NSNumber numberWithInt:index]];

            dispatch_semaphore_signal(semaphore);

        });

    }

在执行到wait方法的时候如果semaphore计数大于等于1.计数-1,返回,程序继续运行。如果计数为0,则等待。这里设置的等待时间是一直等待。dispatch_semaphore_signal(semaphore);计数+1.在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。

参考链接

星光社的戴铭--细说GCD(Grand Central Dispatch)如何用
GCD 深入理解(二)
夏天然后--带你系统学习GCD

你可能感兴趣的:(GCD日记)