021*:GCD 【dispatch_async dispatch_queue_t】 dispatch_group_t、dispatch_barrier_async 、dispatch_semaphore_t、dispatch_once_t、dispatch_apply、dispatch_after、dispatch_source_t 、pthread 、NSThread、NSOperation

问题

dispatch_sync将任务 block通过 push到队列中,然后按照 FIFO去执行。

dispatch_sync造成死锁的主要原因是堵塞的tid和现在运行的tid为同一个

dispatch_async会把任务包装并保存,之后就会开辟相应线程去执行已保存的任务。

semaphore主要在底层维护一个value的值,使用 signal进行 + +1wait进行-1。如果value的值大于或者等于0,则取消阻塞,否则根据timeout参数进行超时判断

dispatch_group底层也是维护了一个 value的值,等待 group完成实际上就是等待value恢复初始值。而notify的作用是将所有注册的回调组装成一个链表,在 dispatch_async完成时判断 value是不是恢复初始值,如果是则调用dispatch_async异步执行所有注册的回调。

dispatch_once通过一个静态变量来标记 block是否已被执行,同时使用加锁确保只有一个线程能执行,执行完 block后会唤醒其他所有等待的线程。

目录

1: GCD简介

2:GCD任务和队列

3:GCD基础使用(6+1种情况)

4:函数执行顺序

5: 调度组 dispatch_group_t

6:栅栏函数dispatch_barrier_sync & dispatch_barrier_async

7:信号量:dispatch_semaphore_t

8:GCD单例-dispatch_once_t

9:GCD 快速迭代方法:dispatch_apply

10:GCD 延时执行方法:dispatch_after

11:定时器 dispatch_source_t

12:pthread

12:NSThread

13:NSOperation

预备

 

正文

 一:GCD简介

GCD,全称Grand Central Dispatch(中央调度中心),C语言开发,提供了很多强大的函数。GCD是非常高效的多线程开发方式,它并不是Cocoa框架的一部分

1:GCD的优势

  1. GCD是苹果公司为多核并行运算提出的解决方案
  2. GCD会动利用更多的CPU内核(比如双核、四核等);
  3. GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
2:GCD核心 将任务添加到队列,并指定任务执行的函数

在日常开发中,GCD一般写成下面这种形式

 dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{
   NSLog(@"GCD基本使用");
});

将上述代码拆分,方便我们来理解GCD核心 主要是由 函数 + 队列 + 任务 构成

//********GCD基础写法********
//创建任务
dispatch_block_t block = ^{
    NSLog(@"hello GCD");
};

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);

//将任务添加到队列,并指定函数执行
dispatch_async(queue, block);
  • 使用dispatch_block_t创建任务
  • 使用dispatch_queue_t创建队列
  • 将任务添加到队列,并指定执行任务的函数dispatch_async

注意

这里的任务是指执行操作的意思,在使用dispatch_block_t创建任务时,主要有以下两点说明

  • 任务使用block封装

  • 任务的block没有参数没有返回值

重点】用一句话总结GCD就是:将任务添加到队列,并指定任务执行的函数

二、GCD任务和队列

在GCD使用中我们只需要做两件事:

  1.定义任务。

  2.将任务添加到队列中。

所以GCD的核心就是dispatch队列和任务。

相信很多初级开发者会对 GCD 任务和队列之间的关系理解含糊不清,实际上队列只是提供了保存任务的容器。为了更好的理解 GCD,很有必要先了解 任务和 队列的概念。

1:GCD任务:dispatch_block_t

任务就是需要执行的操作,是 GCD 中放在 block 中在线程中执行的那段代码。任务的执行的方式有 同步执行和 异步执行两中执行方式。两者的主要区别是 是否等待队列的任务执行结束,以及 是否具备开启新线程的能力

  • 同步执行(sync)同步添加任务到队列中,在队列之前的任务执行结束之前会一直等待;同步执行的任务只能在当前线程中执行,不具备开启新线程的能力。
  • 异步执行(async)异步添加任务到队列中,并需要理会队列中其他的任务,添加即执行;异步执行可以在新的线程中执行,具备开启新的线程的能力。

1.使用dispatch_block_t创建任务

在使用dispatch_block_t创建任务时,主要有以下两点说明

  • 任务使用block封装

  • 任务的block没有参数没有返回值

任务创建好后,等待执行任务的函数将其放入队列中。

拓展:

  执行block,需要调用block(),这步调用,是执行任务的函数内部自动管理
  后面解析dispatch源码时,可以清楚知道调用时机

2: GCD 队列:dispatch_queue_t

1:串行队列 和 并发队列

多线程中所说的队列(Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。每读取一个任务,则动队列中释放一个任务,如下图所示

在GCD中,队列主要分为串行队列(Serial Dispatch Queue) 和并发队列(Concurrent Dispatch Queue)两种,如下图所示 

两者的主要区别是:执行顺序不同,以及开启线程数不同

  • 串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务。(类似单车道,汽车只能一辆辆排队通过
  • 并发队列:可以让对个任务同时执行,也就是开启多个线程,让多个任务同时执行。(类似多车道,同时可以多辆汽车通过

2:队列的创建方法

可以通过 dispatch_queue_create 来创建队列,源码如下:

API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr);
可以看到,需要传入两个参数:第一个参数表示队列的唯一标识符,用于 DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

2.1:创建一个串行队列

dispatch_queue_t seialQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);

2.2:创建一个并发队列

dispatch_queue_t concurrentQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);

2.3:主队列(Main Dispatch Queue)一个特殊的串行队列:所有放在主队列中的任务,都会放到主线程中执行。获取方法如下:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

2.4:全局并发队列:(Global Dispatch Queue)

API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);

可以看到,获取全局队列也需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 :执行任务的函数

执行任务的函数包括同步函数异步函数两种:

3.1 :dispatch_sync同步函数:

  • 必须等待当前语句执行完毕执行下一条语句
  • 开启线程,就在当前线程执行block任务
void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

3.2: dispatch_async异步函数:

  • 不用等待当前语句执行完毕,就可以执行下一条语句
  • 开启线程执行block任务(在新线程执行还是空闲旧线程执行,取决cpu的调度)
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;
    // 任务包装器 - 接受 - 保存 - 函数式
    // 保存 block 
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}

3.3:异步多线程代名词

  • 多线程意义,就是为了适当提高执行效率,开启多个线程"同时"执行多个任务
  • 严格来说,应该是并行异步多线程的代名词。因为只有并行,才支持多通道(车道),才能同时执行多个任务
//异步执行任务
dispatch_async(queue, ^{
   //异步执行的代码
});
 
//同步执行   
dispatch_sync(queue, ^{
   //同步执行的代码
});

4:任务和队列的组合

综上所述,GCD为开发者提供了两种队列(串行/并发)以及两种任务执行方法(同步/异步),所以这里面就有了四种组合。再结合上面提到的主队列和全局并发队列(可以当作是普通的并发队列),现在就有了6种组合:

创建的任务需要放在队列中去执行,同时考虑到主队列的特殊性,那么在不考虑嵌套任务的情况下就会存在同步任务+串行队列、同步任务+并发队列、异步任务+串行队列、异步任务+并发队列、主队列+同步任务、主队列+异步任务六种组合,下面我们来分析下这几种组合。

1. 同步任务+串行队列同步任务不会开启新的线程任务串行执行

2. 同步任务+并发队列同步任务不会开启新的线程,虽然任务在并发队列中,但是系统只默认开启了一个主线程,没有开启子线程,所以任务串行执行

3. 异步任务+串行队列异步任务有开启新的线程任务串行执行

4. 异步任务+并发队列异步任务有开启新的线程任务并发执行

5. 主队列+同步任务主队列是一种串行队列任务在主线程中串行执行将同步任务添加到主队列中会造成追加的同步任务和主线程中的任务相互等待阻塞主线程,导致死锁。

6. 主队列+异步任务主队列是一种串行队列任务在主线程中串行执行,即使是追加的异步任务也不会开启新的线程,任务串行执行

5:死锁情况

1:上边提到的主队列中调用 主队列+同步任务会导致死锁问题
2:使用串行队列的时候,也可能出现阻塞串行队列所在线程的情况发生,从而造成死锁问题。这种情况多见于同一个串行队列的嵌套使用。
在异步任务+串行队列的任务中,又嵌套了当前的串行队列,然后进行同步执行。
dispatch_queue_t queue = dispatch_queue_create("com.thread.demo", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{    // 异步任务 + 串行队列
    dispatch_sync(queue, ^{  // 同步任务 + 串行队列
        sleep(1);              // 模拟耗时操作
        NSLog(@"1");
    });
});

执行上面的代码会导致 串行队列中追加的任务串行队列中原有的任务 两者之间相互等待,阻塞了串行队列,最终造成了串行队列所在的线程(子线程)死锁问题主队列造成死锁也是基于这个原因,所以,这也进一步说明了主队列其实并不特殊。

三:GCD基础使用(6+1种情况)

1 :同步任务+串行队列

不会开启新的线程,任务串行执行的。

- (void)syncSerial {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"串行队列+同步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("sync.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"串行队列+同步执行---结束");
}

运行项目,输出结果如下:

可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程同步执行不具备开启新线程的能力
  • 所有任务都是在 串行队列+同步执行---开始之后, 串行队列+同步执行---结束之前(同步任务需要等待队列的任务执行结束之后才能执行)
  • 任务是按照顺序先后执行的(串行队列每次只有一个任务被执行,执行完一个任务,才会执行下一步操作)

2:同步任务+并发队列

在当前线程中执行任务,不会开启新的线程,执行完一个任务,再执行另一个任务,串行执行

- (void)syncConcurrent {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"同步执行+并发队列---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("sync.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"同步执行+并发队列---结束");
}

运行项目,输出结果如下:

可以看到:

  • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力
  • 所有任务的打印都是在 并发队列+同步执行---开始之后,并发队列+同步执行---结束之前(同步任务需要等待队列的任务执行结束之后才能执行)
  • 任务是按照先后顺序执行的。虽然并发队列可以开启多个线程,并且同时执行多个任务,但是同步任务不具备开启新线程的能力,所以只有当前线程这一个线程,也就不存在并发。当前线程只有等待当前队列中正在执行的任务执行完毕,才能继续执行下一步操作,所以任务只能一个个按照顺序执行。串行执行

3:异步执行+串行队列

具备开启新线程的能力,但是因为任务是在串行队列中执行,当前任务执行完毕,才会执行下一个任务

- (void)asyncSerial {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"串行队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("async.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"串行队列+异步执行---结束");
}

运行项目,输出结果如下:

 

可以看到:

  • 开启了新的线程(异步执行具备开启新线程的能力串行队列只开启一个新线程
  • 所有任务都是在 串行队列+异步执行---开始和 串行队列+异步执行---结束之后开始执行的(异步执行不做任何等待,可以继续执行)
  • 任务是按照先后顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个顺序执行)串行执行

4:异步执行+并发队列

可以开启多个线程,任务交替执行(即我们肉眼看到的同时执行),并发执行

- (void)asyncConcurrent {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"并发队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("async.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"并发队列+异步执行---结束");
}

运行项目,输出结果如下:

可以看到:

  • 除了当前线程(主线程),系统重新开辟了 3 个线程(异步执行具备开启新线程的能力)
  • 任务是交替执行的(并发队列可开启多个线程,同时执行多个任务)
  • 所有任务的执行都是在 并发队列+异步执行---开始和 并发队列+异步执行---结束之后,说明了当前线程并没有等待,而是直接开启了新的线程,在新的线程中执行任务(异步执行不做等待,可以继续执行任务)

主队列

  • 所有放在主队列中的任务,都会放到主线程中执行
  • 获取: dispatch_get_main_queue()
5:同步执行+主队列

同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。

在主线程 中调用 主队列+同步执行

互相等待(死锁)

- (void)syncMain {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"在主线程中主队列+同步执行---开始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"在主线程中主队列+同步执行---结束");
}

可以看到:

在同步执行 + 主队列可以惊奇的发现:

在主线程中使用同步执行 + 主队列,追加到主线程的任务1、任务2、任务3都不再执行了,而且syncMain---end也没有打印,还会报崩溃。这是为什么呢?

这是因为我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完syncMain任务。而syncMain任务需要等待任务1执行完毕,才能接着执行。

那么,现在的情况就是syncMain任务和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain---end也没有打印。

6:异步执行+主队列

只在主线程中执行任务,执行完一个任务,再执行下一个任务,串行执行

- (void)asyncMain {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"主队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"主队列+异步执行---结束");
}

运行项目,输出结果如下:

可以看到:

  • 所有任务都是在主线程中执行的,并没有开启新线程(异步执行具备开启新线程的能力,但因为在主队列,所有任务都是在主线程中)
  • 所有任务都是在 主队列+异步执行---开始和 主队列+异步执行---结束之后执行(异步执行不做任何等待,可以继续执行任务)
  • 任务是按照顺序执行的(主队列是串行队列,每次只有一个任务被执行,一个任务执行完毕,才会执行下一个任务),串行执行

7:在其他线程中调用同步执行 + 主队列,相当于异步执行主队列

不会开启新线程,执行完一个任务,再执行下一个任务。

// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行
 selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

    输出结果:
    2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)}
    2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin
    2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end

在其他线程中使用同步执行 + 主队列可看到:

  • 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
  • 所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
  • 任务是按顺序执行的(主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

为什么现在就不会卡住了呢?

因为syncMain任务放到了其他线程里,而任务1、任务2、任务3都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其他线程中执行到追加任务1到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务2、任务3。所以这里不会卡住线程。

四:函数执行顺序

1:异步函数+并行队列

- (void)interview01{
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
----------打印结果-----------
输出顺序为:1 5 2 4 3

异步函数不会阻塞主队列,会开辟新线程执行异步任务

 

  • 主线程的任务队列为:任务1、异步block1、任务5,其中异步block1会比较耗费性能,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行

  • 异步block1中,任务队列为:任务2、异步block2、任务4,其中block2相对比较耗费性能,任务2任务4是复杂度一样,所以任务2和任务4优先于block2执行

  • 最后执行block2中的任务3

  • 在极端情况下,可能出现 任务2先于任务1任务5执行,原因是出现了当前主线程卡顿或者 延迟的情况

代码修改

  • 【修改1】:将并行队列改成 串行队列,对结果没有任何影响,顺序仍然是 1 5 2 4 3

  • 【修改2】:在任务5之前,休眠2s,即sleep(2),执行的顺序为:1 2 4 3 5,原因是因为I/O的打印,相比于休眠2s,复杂度更简单,所以异步block1 会先于任务5执行。当然如果主队列堵塞,会出现其他的执行顺序

2:异步函数嵌套同步函数 + 并发队列

- (void)interview02{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    //异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        //同步函数
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

----------打印结果-----------
输出顺序为:1 5 2 3 4

分析

  • 任务1 和 任务5的分析同前面一致,执行顺序为 任务1 任务5 异步block

  • 在异步block中,首先执行任务2,然后走到同步block,由于同步函数会阻塞主线程,所以任务4需要等待任务3执行完成后,才能执行,所以异步block中的执行顺序是:任务2 任务3 任务4

3:异步函数嵌套 同步函数 + 串行队列(即同步队列)

开辟线程了。

- (void)interview03{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步函数
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

----------打印结果-----------
输出顺序为:1 5 2 死锁崩溃

分析

  • 首先执行任务1,接下来是异步block,并不会阻塞主线程,相比任务5而言,复杂度更高,所以优先执行任务5,在执行异步block

  • 异步block中,先执行任务2,接下来是同步block同步函数会阻塞线程,所以执行任务4需要等待任务3执行完成,而任务3的执行,需要等待异步block执行完成,相当于任务3等待任务4完成

  • 所以就造成了任务4等待任务3,任务3等待任务4,即互相等待的局面,就会造成死锁,这里有个重点是关键的堆栈 slow

 五:调度组 dispatch_group_t

栅栏函数类似,也是控制任务的执行顺序

  • dispatch_group_create创建组
  • dispatch_group_async进组任务 (自动管理进组出组)
  • dispatch_group_notify进组任务执行完毕通知
  • dispatch_group_wait进组任务执行等待时间
  • dispatch_group_enter进组

  • dispatch_group_leave出组
    进组出组需要成对搭配使用

有以下两种使用方式

  • 【方式一】使用dispatch_group_async + dispatch_group_notify,// 2. 【自动入组和出组】
- (void)cjl_testGroup1{
    /*
     dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间

     应用场景:多个接口请求之后刷新页面
     */
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求一完成");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"请求二完成");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新页面");
    });
}
  • 【方式二】使用dispatch_group_enter + dispatch_group_leave + dispatch_group_notify,// 1. 【手动入组和出组】
- (void)cjl_testGroup2{
    /*
     dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰
     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}
  • 在方式二的基础上增加超时dispatch_group_wait
- (void)cjl_testGroup3{
    /*
     long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

     group:需要等待的调度组
     timeout:等待的超时时间(即等多久)
        - 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕
        - 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕


     返回值:为long类型
        - 返回值为0——在指定时间内调度组完成了任务
        - 返回值不为0——在指定时间内调度组没有按时完成任务

     */
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求一完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"请求二完成");
        dispatch_group_leave(group);
    });
    
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
    NSLog(@"timeout = %ld", timeout);
    if (timeout == 0) {
        NSLog(@"按时完成任务");
    }else{
        NSLog(@"超时");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}
探讨:
dispatch_group_notify内部是异步的执行
dispatch_group_notify队列:决定该block在哪个线程中处理(主:主线程 非主队列:子线程)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"++++++++%@",[NSThread currentThread]);
});
dispatch_group_enterdispatch_group_leave相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify中的任务。这里的dispatch_group_enterdispatch_group_leave组合,其实等同于dispatch_group_async
当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait会阻塞当前线程。

leave多了时,直接crash!

我们知道了
dispatch_group_enter减一
dispatch_group_leave加一
同时,当从os底层获取group的INTERVAL的值为DISPATCH_GROUP_VALUE_1时,会执行一个do-while循环处理新旧状态值,最终dispatch_group_leave会调用_dispatch_group_wake通知group任务block继续往下执行。

六:栅栏函数dispatch_barrier_sync & dispatch_barrier_async

栅栏函数,主要有两种使用场景:串行队列、并发队列

控制任务执行顺序同步

  • dispatch_barrier_async: 前面任务都执行完毕,才会到这里(不会堵塞线程)

  • dispatch_barrier_sync: 堵塞线程,等待前面任务都执行完毕,才放开堵塞。堵塞期间,后面的任务都被挂起等待。

重点:栅栏函数只能控制同一并发队列

  • 栅栏函数只应用在并行队列&异步函数中,它的作用就是在监听多个信号(任务)是否都完成
    串行同步内的信号(任务)本身就是按顺序执行,不需要使用到栅栏函数。)

坑点:栅栏函数为何不能使用dispatch_get_global_queue队列?

  因为global队列中有很多系统任务也在执行。 我们需要dispatch_queue_create手动创建一个纯净队列,放置自己需要执行的任务,再使用栅栏函数监听任务的执行结果

/**
 * 栅栏方法 dispatch_barrier_async
 */
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
}

    输出结果:
    2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)} 

七:信号量:dispatch_semaphore_t

控制GCD最大并发数。(同一时刻可进行的信号(任务)最大个数。)

1:dispatch_semaphore_create: 创建信号量

2:dispatch_semaphore_wait: 信号量等待,发送一个等待信号,信号量-1,当信号量为0阻塞线程,大于0则开始执行后面的逻辑(也就是说执行dispatch_semaphore_wait前如果信号量 <=0 则阻塞,否则正常执行后面的逻辑)当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

3:dispatch_semaphore_signal: 信号量释放发送唤醒信号,信号量会+1

加入了信号量的等待dispatch_semaphore_wait后,一定需要配对加入信号量释放dispatch_semaphore_signal,不然会crash

信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore在实际开发中主要用于:

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

1:Dispatch Semaphore 线程同步

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
    //任务1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务1");
        sleep(1);
        NSLog(@"任务1完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务2");
        sleep(1);
        NSLog(@"任务2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任务3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"执行任务3");
        sleep(1);
        NSLog(@"任务3完成");
        dispatch_semaphore_signal(sem);
    });

信号量的发送与等待,配合使用,可以保证当前任务的按一定顺序去执行,实现线程同步。

2:Dispatch Semaphore 线程安全和线程同步(为线程加锁)

线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全

线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

下面,我们模拟火车票售卖的方式,实现NSThread 线程安全和解决线程同步问题。

场景:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

线程安全(使用 semaphore 加锁)
/**
 * 线程安全:使用 semaphore 加锁
 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 * 售卖火车票(线程安全)
 */
- (void)saleTicketSafe {
    while (1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else { //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
        // 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

    输出结果为:
    2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}
    2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin
    2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票数:49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票数:48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票数:47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    ...
    2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票数:4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票数:3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票数:2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票数:1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票数:0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火车票均已售完
    2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火车票均已售完

可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。

八:GCD单例-dispatch_once_t

单例原理

1:利用static内存中仅一份的特性,保证了对象的唯一性

2:重写allocWithZone的实现,让外界使用alloc创建时,永远返回的是static声明的对象

以下是KCImageManger的核心代码:

#import "KCImageManger.h"
// 保存在常量区
static id instance;

@implementation KCImageManger


/**
 每次类初始化的时候进行调用

 1、+load它不遵循那套继承规则。如果某个类本身没有实现+load方法,那么不管其它各级超类是否实现此方法,系统都不会调用。+load方法调用顺序是:SuperClass -->SubClass --> CategaryClass。
 
 3、+initialize是在类或者它的子类接受第一条消息前被调用,但是在它的超类接收到initialize之后。也就是说+initialize是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是不会被调用的。
 
 4、只有执行+initialize的那个线程可以操作类或类实例,其他线程都要阻塞等着+initialize执行完。
 5、+initialize  本身类的调用都会执行父类和分类实现 initialize方法都会被调多次
 
 */
+ (void)initialize{
    NSLog(@"父类");
    if (instance == nil) {
        instance = [[self alloc] init];
    }
}
/**
 配合上面 也能进行单利
 */
+ (instancetype)manager{
    return instance;
}

/**
 * 所有为类的对象分配空间的方法,最终都会调用到 allovWithZone 方法
 * 下面这样的操作相当于锁死 该类的所有初始化方法
 */
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

/**
 单利
 */
+(instancetype)shareManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

@end

九:GCD 快速迭代方法:dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_applydispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

如果是在串行队列中使用 dispatch_apply,那么就和for循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是异步队列中,dispatch_apply都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的dispatch_group_wait方法。

dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环
应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
1:添加到串行队列中——按序执行
2:添加到主队列中——死锁
3:添加到并发队列中——乱序执行
4:添加到全局队列中——乱序执行
/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

    输出结果:
    2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin
    2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
    2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}
    2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}
    2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}
    2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
    2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}
    2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end

因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是apply---end一定在最后执行。这是因为dispatch_apply函数会等待全部任务执行完毕。

十:GCD 延时执行方法:dispatch_after

我们经常会遇到这样的需求:在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。
需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。
/**
 * 延时执行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}

    输出结果:
    2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main}
    2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin
    2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main}


可以看出:在打印 asyncMain---begin 之后大约 2.0 秒的时间,打印了 after---<NSThread: 0x60000006ee00>{number = 1, name = main}
延时执行3中方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self delay];
}
-(void)delay
{
    NSLog(@"--delay-");
    //延迟执行
//   方法1:
 [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    
// 方法2:
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
    
    //方法3:GCD中的延迟执行
    /* 参数说明
     *
     * 第一个参数:设置时间(GCD中的时间单位是纳秒)
     * 第二个参数:队列(决定block中的任务在哪个线程中执行,如果是主队列就是主线程,否在就在子线程)
     * 第三个参数:设置任务
     * 原理:(哪个简单)
     * A 先把任务提交到队列,然后等两秒再执行 错误
     * B 先等两秒,再把任务提交到队列        正确
     */
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
//        NSLog(@"-----GCD------%@",[NSThread currentThread]);
//    });
}

-(void)run
{
    NSLog(@"run--%@",[NSThread currentThread]);
}

十一:定时器 dispatch_source_t

- (void)use033{
    //倒计时时间
    __block int timeout = 3;
    
    //创建队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    //创建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
    
    //设置1s触发一次,0s的误差
    /*
     - source 分派源
     - start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     - interval 间隔时间
     - leeway 计时器触发的精准程度
     */
    dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
    
     //触发的事件
    dispatch_source_set_event_handler(timer, ^{
        //倒计时结束,关闭
        if (timeout <= 0) {
            //取消dispatch源
            dispatch_source_cancel(timer);
        }else{
            timeout--;
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //更新主界面的操作
                NSLog(@"倒计时 - %d", timeout);
            });
        }
    });
    
    //开始执行dispatch源
    dispatch_resume(timer);
}

因为dispatch_source不依赖于Runloop,而是直接和底层内核交互,准确性更高。

时间准确,可以使用子线程

十二:pthread

pthread的基本使用

#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()

@end

@implementation ViewController

- (IBAction)btnClick:(id)sender {
    
    NSLog(@"mainThread:%@",[NSThread mainThread]);
    
    //pthread创建线程,执行任务
    //01 包含头文件
    //02 创建线程对象
    
    pthread_t thread = nil;
    
    //03 创建线程,执行任务
    /* 参数说明
     *
     * 第一个参数:线程对象 传地址
     * 第二个参数:线程的属性 (优先级)
     * 第三个参数:指向函数的指针
     * 第四个参数:传给第三个参数的(参数)
     */
    pthread_create(&thread, NULL, run, NULL);
}

//技巧:(*)改写成函数的名称,补全参数
void *run(void *str)
{
    //NSLog(@"run-------%@",[NSThread currentThread]);
    
    //把耗时操作放在子线程中执行
    for (int i = 0; i < 1000000; ++i) {
        NSLog(@"%zd---%@",i,[NSThread  currentThread]);
    }
    
    return NULL;
}
//$\color{red}{正在运行}$
@end

十三:NSThread

NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少。

1:创建线程
线程的创建方式主要以下三种方式

  • 通过init初始化方式创建

  • 通过detachNewThreadSelector构造器方式创建

  • 通过performSelector...方法创建,主要是用于获取主线程,以及后台线程

//1、创建
- (void)cjl_createNSThread{
    NSString *threadName1 = @"NSThread1";
    NSString *threadName2 = @"NSThread2";
    NSString *threadName3 = @"NSThread3";
    NSString *threadNameMain = @"NSThreadMain";
    
    //方式一:初始化方式,需要手动启动
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1];
    [thread1 start];
    
    //方式二:构造器方式,自动启动
    [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2];
    
    //方式三:performSelector...方法创建
    [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3];
    
    //方式四
    [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES];
    
}
- (void)doSomething:(NSObject *)objc{
    NSLog(@"%@ - %@", objc, [NSThread currentThread]);
}

2:主线程相关用法

+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程

三种创建线程方式对

方式1:代码量更大|能够拿到线程对象(需要手动开启)

方式2:分离出子线程(不需要手动开启),无法拿到线程对象进行详细设置(名字|优先级)

方式3:开启后台线程 (不需要手动开启),无法拿到线程对象进行详细设置(名字|优先级)

3:线程的属性设置(名称|优先级)

//设置线程的名字
- (void)setName:(NSString *)name;
- (NSString *)name;

//设置线程的优先级  `范围 0.0~1.0`
// 默认是0.5 
//1.0最高的
//优先级更高的线程,被CPU调度到的概率会更高
- (void)setThreadPriority:(double)priority;
- (double)threadPriority;

4:控制线程状态

启动线程
- (void)start; 
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态


阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态


强制停止线程
+ (void)exit;
// 进入死亡状态
注意:一旦线程停止(死亡)了,就不能再次开启任务

5:线程之间的通信

  /* 参数说明
     *
     * 第一个参数:方法选择器  回到主线程要做什么(方法)
     * 第二个参数:调用函数需要传递的参数
     * 第三个参数:是否等待该方法执行完毕才继续往下执行 YES:等待
     */
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

十四:NSOperation 与 NSOperationQueue

1:NSOperation的作用

1:配合使用 NSOperation 和 NSOperationQueue 也能实现多线程编程。

NSOperation 和 NSOperationQueue实现多线程的具体步骤:

  • 先将需要执行的操作或者任务封装到一个NSOperation对象中
  • 然后将NSOperation对象添加到NSOperationQueue
  • 系统会自动将NSOperationQueue中的NSOperation取出来
  • 将取出的NSOperation封装的操作放到一条新线程中执行
2:NSOperation的子类
  • NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类。

  • 使用 NSOperation子类有3种:

  • NSInvocationOperation
  • NSBlockOperation
  • 自定义子类继承 NSOperation,实现内部相应的方法

3:NSInvocationOperation

  • 创建NSInvocationOperation对象.
    -(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
  • 调用start方法开始执行操作.
    -(void)start;
    一旦执行操作,就会调用target的sel方法.
注意:
默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
-(void)invocationOperation
{
    //01 封装操作对象
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
    
    //02 执行操作
     [op1 start];
}

4:NSBlockOperation

创建NSBlockOperation对象
+(id)blockOperationWithBlock:(void (^)(void))block;

-(void)blockOperation
{
    //操作:NSBlockOperation对象
    //任务:block
    
    //01 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];

    //02 执行操作
    [op1 start];
}
通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作.
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    //追加任务
    //当一个操作中的任务数量>1的时候,就会开启子线程和当前线程一起执行任务
    [op3 addExecutionBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];

  [op3 start];

2:NSOperationQueue

  • NSOperationQueue的作用.
  • NSOperation可以调用start方法来执行任务,但默认是同步执行的.
  • 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作.
  • 添加操作到NSOperationQueue中
    -(void)addOperation:(NSOperation *)op;
    -(void)addOperationWithBlock:(void (^)(void))block;

1:操作队列的基本使用(操作 + 队列)

-(void)invocationOperationWithQueue
{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封装操作
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
    
    //03 把操作添加到队列
    [queue addOperation:op1];  
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

注意:

开启几条子线程并不是由操作的数量决定的
-[queue addOperation:op1];该方法内部会自动的调用start方法执行任务

-(void)blockOperationWithQueue
{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    //03 把操作添加到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
    //简便方法:该方法内部首先会把block中的任务封装成一个操作(Operation),然后把该操作直接添加到队列
    [queue addOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
}
简便方法:该方法内部首先会把block中的任务封装成一个操作(Operation),然后把该操作直接添加到队列
[queue addOperationWithBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];

2:最大并发数:同时执行的任务数

最大并发数的相关方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;

3:案例:让多个操作在子线程中顺序执行
把队列的最大并发数设置为 1 ,就可以达到多个操作在子线程中顺序执行的效果。

-(void)changeSerialQueue
{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
    //设置最大并发数对于任务数量大于1的操作是无效的
    //当操作中任务数量>1的时候,会开启多条子线程和当前线程一起工作
//    [op5 addExecutionBlock:^{
//        NSLog(@"6----%@",[NSThread currentThread]);
//    }];
//    
//    [op5 addExecutionBlock:^{
//        NSLog(@"7----%@",[NSThread currentThread]);
//    }];
    
    //设置最大并发数 == 同一时间最多有多少条线程在执行
    //maxConcurrentOperationCount == 0 不能执行任务
    //NSOperationQueueDefaultMaxConcurrentOperationCount = -1 -1指的是一个最大的值(表示不受限制)
    queue.maxConcurrentOperationCount = 1;
    
    //03 把操作添加到队列中
//    [queue addOperation:op1];
//    [queue addOperation:op2];
//    [queue addOperation:op3];
//    [queue addOperation:op4];
//    [queue addOperation:op5];
    

    [queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES];
    
    NSLog(@"------");
    
}
  • 注意点:设置最大并发数对于任务数量大于1的操作是无效的
    创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    封装操作    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
   追加操作
   [op5 addExecutionBlock:^{
        NSLog(@"6----%@",[NSThread currentThread]);
    }];

   [op5 addExecutionBlock:^{
        NSLog(@"7----%@",[NSThread currentThread]);
   }];

   设置最大并发数为1
  queue.maxConcurrentOperationCount = 1;
  
  将操作添加到队列执行任务
  [queue addOperation:op4];
  [queue addOperation:op5];

  另一种添加操作到队列的方法
  [queue addOperations:@[op4,op5] waitUntilFinished:YES];

   NSLog(@"--------");

这时的执行顺序对于 op5 是无效的
  • 注意点:
    1.maxConcurrentOperationCount == 0不能执行任务。
    2.默认NSOperationQueueDefaultMaxConcurrentOperationCount = -1-1指的是一个最大的值(表示不受限制)。
    3.最大并发数 不一定等于所开的线程数:所开多少条线程是由系统决定的。
    4.[queue addOperations:@[op4,op5] waitUntilFinished:YES];这是另一种添加操作到队列的方式,后面参数:YES时,必须执行完队列中的操作,才能执行之后的程序;NO时,可以不用执行完队列中的操作就可以执行程序。

4:队列的取消、暂停、恢复

  • 取消队列的所有操作
  • -(void)cancelAllOperations;
  • 提示:也可以调用NSOperation- (void)cancel方法取消单个操作
  • 暂停和恢复队列
  • -(void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
    -> -(BOOL)isSuspended;
- (void)start{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封装操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"1--%zd--%@",i,[NSThread currentThread]);
        }
        
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"2--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"3--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"4--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    //设置最大并发数
    queue.maxConcurrentOperationCount = 1;
    
    //03 添加到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    
    self.queue = queue;
}

- (IBAction)suspendBtnClick:(id)sender {
    
    //暂停 YES
    //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕
    //操作是有状态的
    [self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
    
    //恢复
    [self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消 取消所有的操作
    //只能取消队列中处理等待状态的操作
    [self.queue cancelAllOperations];
}

5:自定义操作、自定义线程

  • 自定义NSOperation的步骤很简单
  • 重写- (void)main方法,在里面实现想执行的任务
  • 重写- (void)main方法的注意点
  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放>池)
  • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
#import "ViewController.h"
#import "KXOperation.h"
#import "KXGThread.h"

@interface ViewController ()

@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self test2];
}

-(void)thread
{
    //自定义线程
    XMGThread *thread = [[XMGThread alloc]init];
    [thread start];
}

- (IBAction)startBtnClick:(id)sender {
    
//    [self test1];
    [self test2];
    
}
- (IBAction)suspendBtnClick:(id)sender {
    
    //暂停 YES
    //只能暂停当前操作后面的操作,当前操作不可分割必须执行完毕
    //操作是有状态的
    [self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
    
    //恢复
    [self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消 取消所有的操作
    //只能取消队列中处理等待状态的操作
    [self.queue cancelAllOperations];
}

-(void)test2
{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封装操作(执行任务)
    XMGOperation *op = [[XMGOperation alloc]init];
    
    //03 把操作添加到队列
    [queue addOperation:op];   //内部会调用start方法 ->main
    
    //自定义操作的好处:代码复用
    self.queue = queue;
}
@end

-------------------------------------------------------------------
#import "KXGOperation.h"

@implementation KXGOperation

//重写内部的main方法类告诉自定义的操作任务是什么?
-(void)main
{
    // NSLog(@"main----%@",[NSThread currentThread]);
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"1---%zd--%@",i,[NSThread currentThread]);
    }
    
    //官方建议:在自定义操作的时候每执行完一个耗时操作就判断一下当前操作是否被取消,如果被取消就直接返回
    if (self.isCancelled) {
        return;
    }
    
    NSLog(@"++++++++++++++++");
    
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"2---%zd--%@",i,[NSThread currentThread]);
    }
    
    if (self.isCancelled) {
        return;
    }
     NSLog(@"++++++++++++++++");
    
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"3d---%zd--%@",i,[NSThread currentThread]);
    }
    
}
@end

-------------------------------------------------------------------
#import "KXGThread.h"

@implementation KXGThread

-(void)main
{
    NSLog(@"MAIN----%@",[NSThread currentThread]);
}
@end

6:操作依赖

  • NSOperation之间可以设置依赖来保证执行顺序

比如一定要让操作A执行完后,才能执行操作B,可以这么写。
[operationB addDependency:operationA];// 操作B依赖于操作A。

  • 可以在不同queueNSOperation之间创建依赖关系。

注意:不能相互依赖
比如:A依赖BB依赖A

7:操作的监听
  • 可以监听一个操作的执行完毕
    -(void (^)(void))completionBlock;
    -(void)setCompletionBlock:(void (^)(void))block;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //01 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    //02 封装操作对象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
    //监听任务执行完毕
    op4.completionBlock = ^{
        
        NSLog(@"小4任务已完成");
    };
    
    [op1 setCompletionBlock:^{
         NSLog(@"老幺任务已完成");
    }];
    
    //03 设置操作依赖:4->3->2->1->5
    //⚠️ 不能设置循环依赖,结果就是两个任务都不会执行
    [op5 addDependency:op1];
    [op1 addDependency:op2];
    //[op2 addDependency:op1];
    [op2 addDependency:op3];
    [op3 addDependency:op4];
    
    //04 把操作添加到队列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue2 addOperation:op5];
    
    
}

8:线程间的通信

01 创建队列
NSOperationQueue *queue  =[[NSOperationQueue alloc]init];

02封装操作
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{

            执行任务
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{

              回到主线程           

       }];

 }];

03 把操作添加到队列
[queue addOperation:download];

9:GCD和NSOperation的对比:

1)GCD是纯C语言的API,而操作队列则是Object-C的对象。
2)在GCD中,任务用块(block)来表示,而块是个轻量级的数据结构;
   相反操作队列中的『操作』NSOperation则是个更加重量级的Object-C对象。
3)具体该使用GCD还是使用NSOperation需要看具体的情况

 
NSOperation和NSOperationQueue的好处有:
1)NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
2)NSOperation可以方便的指定操作间的依赖关系。
3)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)
4)NSOperation可以方便的指定操作优先级。操作优先级表示此操作与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的后执行。
5)通过自定义NSOperation的子类可以实现操作重用.

10:使用Crearte函数创建的并发队列和全局并发队列的主要区别:

1)全局并发队列在整个应用程序中本身是默认存在的并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Create函数是实打实的从头开始去创建一个队列。
 
2)在iOS6.0之前,在GCD中凡是使用了带Create和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。
 
3)在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)
4)其它区别涉及到XNU内核的系统级线程编程,不一一列举。

注意

 

引用

 1:iOS-底层原理 26:GCD 之 函数与队列

2:OC底层原理二十六:GCD详解(上)

3:十九、iOS多线程:GCD详解(上)

4:OC底层知识点之-多线程(二)GCD上篇

5: iOS GCD之任务与队列

6:iOS 底层探索:多线程GCD的使用

7:GCD之函数与队列初探

8:函数与队列

9:iOS-底层原理 27:GCD 之 NSThread & GCD & NSOperation

10:OC底层原理二十六:GCD详解(上)

11:iOS开发之多线程:Pthread、NSThread、GCD、NSOperation、NSOperationQueue

12:二十、iOS多线程:GCD详解(下)

13:iOS 底层探索:多线程GCD的使用

14:iOS 底层探索:Dispatch_source & @Synchronized

15:GCD底层

16:GCD底层主要

posted on 2020-12-03 11:20  风zk  阅读(165)  评论(0编辑  收藏  举报

导航