iOS多线程方案

iOS开发中需要将一些耗时操作放到子线程中去执行, 防止阻塞主线程造成卡顿现象, 这时就用到了多线程.
本篇文章主要讲解iOS中多线程的方案、GCD的使用、NSOperation的使用、死锁现象以及死锁的本质、多线程的同步方案、文件的多读单写操作等.

一. 概念

iOS开发中, 最常用的两种多线程方案就是GCDNSOperation了.

多线程方案 简介 语言 生命周期
GCD 充分利用系统的多核 C 自动管理
NSOperation 对GCD的封装, 更加面向对象, 增加了一下实用功能 OC 自动管理

在理解多线程之前需要对以下概念进行了解

  • 进程: 简单理解为正在运行的一个App就是一个进程.
  • 线程: 线程用来执行任务, 一个进程中可以有多个线程.
  • 同步执行: 阻塞当前线程, 在当前线程中执行任务, 不具备开启线程的能力.
  • 异步执行: 不阻塞当前线程, 具备开启线程的能力(不一定不开启新线程)
  • 串行队列: 从队列中取出任务的顺序是FIFO, 上一个任务执行完成之后再执行下一个任务
  • 并发队列: 从队列中取出任务的顺序是FIFO, 多个任务同时执行, 任务完成的顺序不确定(个人理解为, 从队列中取任务分发到线程去执行)
并发队列 串行队列 主队列
同步执行(sync) 没有开启线程, 串行执行任务 没有开启线程, 串行执行任务 没有开启线程, 串行执行任务
异步执行(async) 有开启线程, 并发执行任务 有开启线程, 串行执行任务 没有开启线程, 串行执行任务

二. GCD

GCD中的队列有四种:

    // 串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_SERIAL);
    // 并发队列
    dispatch_queue_t queue2 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_CONCURRENT);
    // 主队列
    dispatch_queue_t queue3 = dispatch_get_main_queue();
    // 全局并发队列
    dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

在使用dispatch_queue_create创建队列时, 第一个参数是队列的字符串标签(队列的唯一标识), 格式为com.example.myqueue, 参数可选, 默认为NULL. 在iOS4.3以后第二个参数传入DISPATCH_QUEUE_SERIAL用于创建串行队列, DISPATCH_QUEUE_CONCURRENT用于创建并发队列, 在iOS4.3之前必须传入NULL.
使用dispatch_get_main_queue获取和主线程相关联的主队列, 注意在主队列中使用 dispatch_suspend, dispatch_resume, dispatch_set_context函数, 无效果.
使用dispatch_get_global_queue获取全局并发队列, 第一个参数用于设置队列的优先级, 有下列四个值, 第二个参数传入0.

#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

2.1 同步执行, 串行队列

- (void)gcdTest1 {
    
    // 串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务2-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务3-%@", [NSThread currentThread]);
        }
    });
    
}

同步执行不具备开启线程的能力, 串行队列任务一个一个执行.

2018-09-03 20:54:20.057889+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务1-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058138+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务1-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058289+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务1-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058442+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务2-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058588+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务2-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058740+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务2-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.058882+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务3-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.059559+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务3-<NSThread: 0x60400007be40>{number = 1, name = main}
2018-09-03 20:54:20.060899+0800 ThredDemo[23108:819649] 同步执行-串行队列-任务3-<NSThread: 0x60400007be40>{number = 1, name = main}

2.2 异步执行, 串行队列

- (void)gcdTest2 {
    
    // 串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务2-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务3-%@", [NSThread currentThread]);
        }
    });
    
}

异步执行具备开启线程的能力, 串行队列任务会一个执行完成再执行另一个.

2018-09-03 20:55:09.989752+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务1-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.990425+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务1-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.990612+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务1-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.990847+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务2-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.991174+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务2-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.991261+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务2-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.991349+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务3-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.991428+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务3-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}
2018-09-03 20:55:09.991548+0800 ThredDemo[23127:820426] 同步执行-串行队列-任务3-<NSThread: 0x60400026ffc0>{number = 3, name = (null)}

2.3 同步执行, 并发队列

- (void)gcdTest3 {
    
    // 并发队列    
    dispatch_queue_t queue1 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务2-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务3-%@", [NSThread currentThread]);
        }
    });
    
}

同步执行不具备开启线程能力, 在当前线程中执行.

2018-09-03 20:55:37.926704+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务1-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.926881+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务1-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.927068+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务1-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.927418+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务2-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.927712+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务2-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.928126+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务2-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.928506+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务3-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.929594+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务3-<NSThread: 0x60400007f180>{number = 1, name = main}
2018-09-03 20:55:37.930951+0800 ThredDemo[23154:821391] 同步执行-串行队列-任务3-<NSThread: 0x60400007f180>{number = 1, name = main}

2.4 异步执行, 并发队列

- (void)gcdTest4 {
    
    // 并发队列
    dispatch_queue_t queue1 = dispatch_queue_create("com.onealon.gcdTest1", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务2-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"同步执行-串行队列-任务3-%@", [NSThread currentThread]);
        }
    });
    
}

异步执行具备开启线程的能力, 并发执行任务可以同时执行多个.

2018-09-03 20:57:57.697746+0800 ThredDemo[23206:823843] 同步执行-串行队列-任务2-<NSThread: 0x604000261c40>{number = 7, name = (null)}
2018-09-03 20:57:57.697818+0800 ThredDemo[23206:823621] 同步执行-串行队列-任务1-<NSThread: 0x6000002733c0>{number = 6, name = (null)}
2018-09-03 20:57:57.697916+0800 ThredDemo[23206:823844] 同步执行-串行队列-任务3-<NSThread: 0x604000261fc0>{number = 8, name = (null)}
2018-09-03 20:57:57.699438+0800 ThredDemo[23206:823843] 同步执行-串行队列-任务2-<NSThread: 0x604000261c40>{number = 7, name = (null)}
2018-09-03 20:57:57.700144+0800 ThredDemo[23206:823621] 同步执行-串行队列-任务1-<NSThread: 0x6000002733c0>{number = 6, name = (null)}
2018-09-03 20:57:57.700292+0800 ThredDemo[23206:823844] 同步执行-串行队列-任务3-<NSThread: 0x604000261fc0>{number = 8, name = (null)}
2018-09-03 20:57:57.700618+0800 ThredDemo[23206:823843] 同步执行-串行队列-任务2-<NSThread: 0x604000261c40>{number = 7, name = (null)}
2018-09-03 20:57:57.700776+0800 ThredDemo[23206:823621] 同步执行-串行队列-任务1-<NSThread: 0x6000002733c0>{number = 6, name = (null)}
2018-09-03 20:57:57.700811+0800 ThredDemo[23206:823844] 同步执行-串行队列-任务3-<NSThread: 0x604000261fc0>{number = 8, name = (null)}

2.5 一次性函数 dispatch_once

- (void)gcdTest5 {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"任务1--只执行了一次哦");
    });
}

在进程的生命周期内, 任务1只会执行一次, 并且在多线程访问时, 一次性函数是安全的.
一次性函数dispatch_once经常用于创建单例

+ (instancetype)sharedInstance
{
    static CTMediator *mediator;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mediator = [[CTMediator alloc] init];
    });
    return mediator;
}

之前在做App启动时间优化时, 需要将load中的代码执行时间向后推迟, 这时可以使用initializedispatch_once方法组合, 将load中的代码推迟到需要使用的时候再调用.

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 做一些初始化的操作.
    });
}

2.6 延迟函数 dispatch_after

- (void)gcdTest6 {
    /*
     等到指定的时间将任务block异步添加到指定的队列中.
     */
    NSLog(@"begin---");
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), queue, ^{
        NSLog(@"延迟函数--%@", [NSThread currentThread]);
    });
    NSLog(@"end---");
}

执行结果:

2018-09-04 10:50:42.074319+0800 ThredDemo[3066:85334] begin---
2018-09-04 10:50:42.079407+0800 ThredDemo[3066:85334] end---
2018-09-04 10:50:44.278166+0800 ThredDemo[3066:85413] 延迟函数--<NSThread: 0x604000462640>{number = 3, name = (null)}

延时函数并非是在指定的时间后开始执行任务block, 而是在指定的时间后将任务block异步添加到指定的队列中, 然后再等待从队列中取出任务block放入线程中去执行, 如果block分配的线程被sleep了一段时间, 那么这个延迟函数就不太准确了, 所以延迟函数dispatch_after存在一定的时间误差.

2.7 栅栏函数 dispatch_barrier_async

- (void)gcdTest7 {
 
    NSLog(@"begin---");
    
    // 手动创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.onealon.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"耗时任务1--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"耗时任务2--%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"耗时任务1和任务2执行完成以后--任务3--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"任务4--%@", [NSThread currentThread]);
    });
    
    NSLog(@"end---");
}

执行结果:

2018-09-04 10:55:25.360533+0800 ThredDemo[3148:88215] begin---
2018-09-04 10:55:25.365543+0800 ThredDemo[3148:88215] end---
2018-09-04 10:55:25.365730+0800 ThredDemo[3148:88889] 耗时任务2--<NSThread: 0x604000271640>{number = 4, name = (null)}
2018-09-04 10:55:25.365730+0800 ThredDemo[3148:88249] 耗时任务1--<NSThread: 0x600000073bc0>{number = 3, name = (null)}
2018-09-04 10:55:25.366010+0800 ThredDemo[3148:88249] 耗时任务1和任务2执行完成以后--任务3--<NSThread: 0x600000073bc0>{number = 3, name = (null)}
2018-09-04 10:55:25.367820+0800 ThredDemo[3148:88249] 任务4--<NSThread: 0x600000073bc0>{number = 3, name = (null)}

当栅栏函数中的任务block到达并发队列queue的最前边时, 任务block并不会立即执行, 它会等待并发队列中正在执行的任务执行完成以后才执行栅栏函数的任务. 任何在栅栏函数以后添加到并发队列中的任务都会等待栅栏函数中的任务执行完成以后才执行.

使用dispatch_barrier_async需要注意:

  • 栅栏函数中拦截的任务必须和耗时任务在同一个队列中.
  • 栅栏函数中的队列必须是使用dispatch_queue_create手动创建的并发队列
    如果是串行队列或者全局并发队列, 那么栅栏函数就相当于dispatch_async函数, 不会起到拦截效果.

2.8 队列组

- (void)gcdTest8 {
    
    NSLog(@"begin---");
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@",[NSThread currentThread]);
        }
    });

    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@",[NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue2, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务3-%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"end---");
}

执行结果:

2018-09-04 11:12:34.087597+0800 ThredDemo[3435:99939] begin---
2018-09-04 11:12:34.088345+0800 ThredDemo[3435:99939] end---
2018-09-04 11:12:34.088427+0800 ThredDemo[3435:99995] 任务1-<NSThread: 0x60400046e100>{number = 3, name = (null)}
2018-09-04 11:12:34.088427+0800 ThredDemo[3435:100452] 任务2-<NSThread: 0x600000266240>{number = 4, name = (null)}
2018-09-04 11:12:34.090944+0800 ThredDemo[3435:100452] 任务2-<NSThread: 0x600000266240>{number = 4, name = (null)}
2018-09-04 11:12:34.090944+0800 ThredDemo[3435:99995] 任务1-<NSThread: 0x60400046e100>{number = 3, name = (null)}
2018-09-04 11:12:34.091614+0800 ThredDemo[3435:99995] 任务1-<NSThread: 0x60400046e100>{number = 3, name = (null)}
2018-09-04 11:12:34.091634+0800 ThredDemo[3435:100452] 任务2-<NSThread: 0x600000266240>{number = 4, name = (null)}
2018-09-04 11:12:34.091777+0800 ThredDemo[3435:100452] 任务3-<NSThread: 0x600000266240>{number = 4, name = (null)}
2018-09-04 11:12:34.104841+0800 ThredDemo[3435:100452] 任务3-<NSThread: 0x600000266240>{number = 4, name = (null)}
2018-09-04 11:12:34.107901+0800 ThredDemo[3435:100452] 任务3-<NSThread: 0x600000266240>{number = 4, name = (null)}

dispatch_group_notify会监听队列组group中的任务, 当队列组中的任务执行完成以后, 会将dispatch_group_notify中的任务添加到指定队列中.
如果队列组中没有任何关联的任务, dispatch_group_notify会立即将任务添加到指定队列.

dispatch_group_notifydispatch_barrier_async的区别:

  • dispatch_barrier_async只能拦截在同一个队列中的任务
  • dispatch_group_notify可以监听不同队列中的任务

2.9 GCD中队列的区别

主队列:

  • 和主线程相关联的队列
  • 不能调用dispatch_suspend, dispatch_resume, dispatch_set_context方法
  • 当gcd代码在主队列执行时, 会将block中的代码添加到runloop中, runloop调用__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__执行block代码(这部分还需要在研究一下)

全局并发队列:

  • 全局并发队列整个进程共享, 有高 中 低 后台 四个优先级
  • 手动创建的队列可以取消, 全局队列不能取消(Calls to the dispatch_suspend, dispatch_resume, and dispatch_set_context functions have no effect on the returned queues.)
  • 全局并发队列不能使用在栅栏函数dispatch_barrier_async

三. NSOperation

NSOperation的使用步骤:

  1. 使用NSOperation的子类创建操作operation
  2. 使用NSOperationQueue创建队列queue
  3. 将操作operation添加到队列queue中, 系统会自动去除队列中的任务执行.

NSOperation是一个抽象类, 在使用的时候可以使用它的子类NSBlockOperationNSInvocationOperation, 或者创建NSOperation的子类.
NSOperationQueue分两种队列, 一种是通过[NSOperationQueue mainQueue]获取的主队列(主队列是串行队列), 另一种是通过[[NSOperationQueue alloc] init]手动创建的队列. 手动创建的队列可以通过设置maxConcurrentOperationCount属性控制器队列是串行还是并发, maxConcurrentOperationCount=1是串行队列.

3.1 NSBlockOperation

- (void)operationTest1 {
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@",[NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

3.2 NSInvocationOperation

- (void)operationTest2 {
    
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation1Selector) object:nil];
    
    
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operation2Selector) object:nil];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

- (void)operation1Selector {
    for (int i = 0; i < 3; i++) {
        NSLog(@"任务1-%@",[NSThread currentThread]);
    }
}

- (void)operation2Selector {
    for (int i = 0; i < 3; i++) {
        NSLog(@"任务2-%@",[NSThread currentThread]);
    }
}

3.3 匿名操作

- (void)operationTest3 {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@",[NSThread currentThread]);
        }
    }];
    
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@",[NSThread currentThread]);
        }
    }];

}

3.4 取消 暂停 恢复操作

- (void)operationTest5 {
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@",[NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 取消某一个操作
//    [operation1 cancel];
    
    // 暂停队列
    [queue setSuspended:YES];

    // 取消队列中的操作
//    [queue cancelAllOperations];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [queue setSuspended:NO];
    });
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    
}

3.5 设置依赖

- (void)operationTest4 {
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@",[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务3-%@",[NSThread currentThread]);
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [operation2 addDependency:operation3];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
}

3.6 线程间通讯

- (void)operationTest6 {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSLog(@"在子线程中执行--任务1--%@", [NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"在主线程中执行--任务2--%@", [NSThread currentThread]);
        }];
    }];
    
}

四. 线程死锁问题

在使用多线程的时候要特别注意线程死锁的问题, 以下代码均在主线程执行, 会死锁吗?

- (void)interview1 {
    NSLog(@"执行任务1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
}

会造成线程死锁的现象
gcd代码同步执行, 会阻塞当前线程, 也就是任务2执行完成以后才会执行任务3. 主队列是串行队列, 当一个任务执行完成以后才会执行另一个任务, 也就是任务3执行完成以后才会执行任务2. 所以任务2和任务3就会一直相互等待, 形成死锁.

- (void)interview2 {
    NSLog(@"执行任务1");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
}

不会造成死锁
gcd代码异步执行, 不会阻塞当前线程, 个人理解为gcd代码会将任务2添加到主队列中, 而interview2也是在主队列中, 主队列中的任务执行顺序是一个一个执行, 所以, 执行顺序是1-3-2

- (void)interview3
{
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}

任务2不会死锁, 任务3会死锁.
queue是串行队列, 执行任务是一个一个执行, async代码是异步执行, 会开启新线程, 任务2是在子线程中执行, sync代码是同步执行, 不会开启新线程, 阻塞当前线程, 任务3会等待队列中的async任务执行完成以后, 再取出sync代码执行, 而sync代码已经阻塞了当前线程, 所以造成死锁.

- (void)interview4
{
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue2, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}

不会造成死锁
queue和queue2虽然都是串行队列, async+queue会开启新线程, sync虽然阻塞当前线程, 但是sync的任务是从queue2队列中获取, 不会造成死锁

- (void)interview5
{
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
}

不会造成死锁
queue是并发队列, 并发队列可以同时执行多个任务, async+queue会开启新线程, 在子线程执行async代码, sync虽然会阻塞当前线程, 但是queue是并发队列, 所以执行任务3时不必等待async代码从队列中执行完成.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容