多线程之NSOperation

warning tip

  • GCD中的global并行队列是单例,只是在栅栏队列里由于不知名原因,表现得不是一个队列,但是实际还是一个单例
  • 开发中要自定义一个全局队列的话使用正常的单例就可以了

  • NSOperation有两个子类,NSBlockOperationNSInvocationOperation,正常情况它们是这样使用的
  • NSBlockOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1. 封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
// 主线程
NSLog(@"1---%@", [NSThread currentThread]);
}];
// 2.追加其它任务
// 注意: 在没有队列的情况下, 如果给BlockOperation追加其它任务, 那么其它任务会在子线程中执行
[op1 addExecutionBlock:^{
NSLog(@"2---%@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"3---%@", [NSThread currentThread]);
}];
// 3.启动任务
[op1 start];
  • NSInvocationOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 注意: 父类不具备封装操作的能力
// NSOperation *op = [[NSOperation alloc] init];
// 1.封装任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2.要想执行任务必须调用start
[op1 start];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
[op2 start];
- (void)run
{
NSLog(@"%@", [NSThread currentThread]);
}
- (void)run2
{
NSLog(@"%@", [NSThread currentThread]);
}

  • 上面的代码并没有主动控制线程,NSInvocationOperation的执行线程都是在主线程中,NSBlockOperation第一个block在主线程中,追加的则在子线程中,一般开发中我们不会这样直接使用,需要配合队列使用
  • 配合队列很简单

  • NSOperationQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 == %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2 == %@", [NSThread currentThread]);
}];
// 注意: 如果是使用block来封装任务, 那么有一种更简便的方法
// 只要利用队列调用addOperationWithBlock:方法, 系统内部会自动封装成一个NSBlockOperation \
然后再添加到队列中
[queue addOperationWithBlock:^{
NSLog(@"3 == %@", [NSThread currentThread]);
}];
// 3.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];
  • NSInvocationOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 1.创建队列
/*
GCD中有哪些队列:
并发: 自己创建, 全局
串行: 自己创建, 主队列
NSOperationQueue:
主队列: mainQueue
自己创建: 会在子线程中执行
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
// 只要是自己创建的队列, 就会在子线程中执行
// 而且默认就是并发执行
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
// 3.添加任务到队列中
// 只要将任务添加到队列中, 队列会自动调用start
[queue addOperation:op1];
[queue addOperation:op2];
- (void)download1
{
NSLog(@"1 == %@", [NSThread currentThread]);
}
- (void)download2
{
NSLog(@"2 == %@", [NSThread currentThread]);
}
  • 上面的都会自动创建多个线程同时进行,所以都是并行队列,如果想要串行执行,就把最大并行数设置为1就可以了
1
queue.maxConcurrentOperationCount = 1;
  • NSOperation有个好处就是可以自定义任务
  • 创建一个继承自NSOperation的对象
1
@interface HJSOperation : NSOperation
  • 然后.m文件里把想要执行的任务填写在重写的main函数里
1
2
3
4
5
6
7
8
/*
只要将任务添加到队列中, 那么队列在执行自定义任务的时候
就会自动调用main方法
*/
- (void)main
{
NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}
  • 自定义NSOperation的操作方法
1
2
3
4
5
6
7
8
9
10
11
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建任务
// 自定义任务的好处: 提高代码的复用性
XMGOperation *op1 = [[XMGOperation alloc] init];
XMGOperation *op2 = [[XMGOperation alloc] init];
// 3.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];

NSOperation的暂停、恢复和取消

  • NSOperation中还有暂停(suspended),恢复(!suspended)和取消(cancelAllOperations)功能,其实操作很简单,就拿到对应的队列操作就可以了,但是有些注意点
  • 如果是一个串行队列,里面有多个任务,当队列设置为取消或者暂停的时候,那么正在执行的任务不会暂停,知道执行结束,下一个任务不会再执行
  • 如下面的多个任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 1;
// 2.创建任务
/*
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"1 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"2 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"3 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"4 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"5 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
[self.queue addOperationWithBlock:^{
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// [NSThread sleepForTimeInterval:1];
// NSLog(@"6 == %@", [NSThread currentThread]);
for (int i = 0; i < 10000; i++) {
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}];
*/
  • 暂停代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// [self operation1];
/*
// 只要设置队列的suspended为YES, 那么就会暂停队列中其它任务的执行
// 也就是说不会再继续执行没有执行到得任务
// 只要设置队列的suspended为NO, 那么就会恢复队列中其它任务的执行
if (self.queue.suspended) {
self.queue.suspended = NO;
}else
{
// 注意: 设置为暂停之后, 不会立即暂停
// 会继续执行当前正在执行的任务, 直到当前任务执行完毕, 就不会执行下一个任务了
// 也就是说, 暂停其实是暂停下一个任务, 而不能暂停当前任务
// 注意: 暂停是可以恢复的
self.queue.suspended = YES;
}
*/
// 取消队列中所有的任务的执行
// 取消和暂停一样, 是取消后面的任务, 不能取消当前正在执行的任务
// 注意: 取消是不可以恢复的
[self.queue cancelAllOperations];
}
  • 由于暂停受到如此的限制,所以即使苹果也建议我们在自定义的任务中,做耗时操作时,可以分段做出一些判断,当察觉到线程状态改变时,及时主动停止任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)main
{
// 耗时操作1
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
NSLog(@"++++++++++++++++++++++++++++++++++++++");
if (self.isCancelled) {
return;
}
// 耗时操作2
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
if (self.isCancelled) {
return;
}
NSLog(@"++++++++++++++++++++++++++++++++++++++");
// 好所操作3
for (int i = 0; i < 10000; i++) { // 500
NSLog(@"%i ==== %@", i, [NSThread currentThread]);
}
}
  • 自定义任务只能访问isCancelled属性,在几个耗时代码之间插入判断,当线程被取消时,主动return

线程依赖

  • 这里有点类似于GCD中的栅栏,但是它更厉害一些,栅栏的使用必须要在同一队列中,而NSOperation没有这样的限制
  • GCD栅栏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.520it.lmj", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1-------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-------%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-------%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue2, ^{
NSLog(@"4-------%@", [NSThread currentThread]);
});
  • 上面的栅栏无法起到限制作用,栅栏依然有可能在前面的异步之前执行,因为必须要在同一队列中
  • 再来看看NSOperation的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
// 2.创建任务
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]);
for (int i = 0; i < 1000; i++) {
NSLog(@"%i", i);
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"5-------%@", [NSThread currentThread]);
}];
// 3.添加依赖
[op5 addDependency:op1];
[op5 addDependency:op2];
[op5 addDependency:op3];
[op5 addDependency:op4];
// 4.监听op4什么时候执行完毕
op4.completionBlock = ^{
NSLog(@"op4中所有的操作都执行完毕了");
};
// 4.添加任务到队列
[queue addOperation:op1];
[queue addOperation:op2];
[queue2 addOperation:op3];
[queue2 addOperation:op4];
[queue addOperation:op5];
  • 上面的代码无论执行多少次,op5都只会在最后执行,并且没有队列限制

线程间的通信

  • 知道了怎么添加线程依赖后,就可以来学习线程通信了
  • 因为线程通信牵扯到线程的先后执行,只有当耗时操作完成后,才能来通知主线程刷新UI,这就是线程通信
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1.创建一个新的队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加任务(操作)
[queue addOperationWithBlock:^{
// 2.1在子线程中下载图片
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.2回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
  • 上面的代码没有体现出线程依赖的使用,下面弄个有线程依赖的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 1.创建一个队列
// 一般情况下, 在做企业开发时候, 都会定义一个全局的自定义队列, 便于使用
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加一个操作下载第一张图片
__block UIImage *image1 = nil;
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
image1 = [UIImage imageWithData:data];
}];
// 3.添加一个操作下载第二张图片
__block UIImage *image2 = nil;
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img1/M02/EE/B5/CgAPDE-kEtqjE8CWAAg9m-Zz4qo025-22365300.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
image2 = [UIImage imageWithData:data];
}];
// 4.添加一个操作合成图片
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
UIImage *res = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 5.回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = res;
}];
}];
// 6.添加依赖
[op3 addDependency:op1];
[op3 addDependency:op2];
// 7.添加操作到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
  • 不管怎样,UI的刷新必定在数据下载线程完毕之后

    同步在串行队列里造成死锁的情况解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /*
    1:
    - (void)viewDidLoad {
    //dispatch_sync(dispatch_get_main_queue(), ^{
    //NSLog(@3);
    //死锁原因
    //1:dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁
    //2:dispatch_sync是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block,所以就会发生死锁。
    //});
    //dispatch_async(dispatch_get_global_queue(), ^{
    //async 在主线程中 创建了一个异步线程 加入 全局并发队列,async 不会等待block 执行完成,立即返回
    NSLog(@2);//不会造成死锁;
    });
    }
    分析这段代码:view DidLoad 在主线程中,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到后面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却
    在等待sync返回,去执行后续工作,从而造成死锁。
    2:
    dispatch_sync 和 dispatch_async 区别:
    dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
    dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,即阻塞当前线程,等待 block同步执行完成。
    3:
    GCD Queue 分为三种:
    1,The main queue :主队列,主线程就是在个队列中。
    2,Global queues : 全局并发队列。
    3,用户队列:是用函数 dispatch_queue_create 创建的自定义队列
    */
  • 队列是管理任务的,当我们用dispatch_async或者dispatch_sync这些函数加入队列中的时候,只是把任务塞给了队列

  • 然后由队列来分配线程执行任务
  • 那么async与sync其实指的就是任务的优先级
  • 在串行队列中,任务的执行是按顺序的,那么排在最前面的任务优先级最高
  • 在并行队列中,任务执行不按顺序
  • dispatch_sync本身是一个函数,它执行同时又要执行它的代码块,这些任务都是相同优先级,会对线程资源造成抢占
  • 而在dispatch_async没有任务的顺序强制性,所以,不会造成资源抢占