iOS多线程方案

2018-09-03  本文已影响17人  OneAlon

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

一. 概念

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

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

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

并发队列 串行队列 主队列
同步执行(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需要注意:

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的区别:

2.9 GCD中队列的区别

主队列:

全局并发队列:

三. 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代码从队列中执行完成.

上一篇下一篇

猜你喜欢

热点阅读