第九篇:Objective-C 知识回顾多线程

多线程大纲

9.1.同步/异步 和 串行/并发

问题一:同步/异步 和 串行/并发组合有哪些?
  • dispatch_sync(serial_queue, ^{//任务});
  • dispatch_async(serial_queue, ^{//任务});
  • dispatch_sync(concurrent_queue, ^{//任务});
  • dispatch_async(concurrent_queue, ^{//任务});
问题二:分析下面代码是否存在问题?
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Hello world!");
    });
}
  • 会引起死锁
  • 因为 Block 任务和 viewDidLoad 任务都要在主线程中执行,并且在同一个串行队列中,而这两个任务需要相互等待对方任务的完成才能继续进行,就造成了等待死锁。
问题三:分析下面代码是否存在问题?
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t serialQueue = dispatch_queue_create("apple.searial", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"Hello world!");
    });
}
  • 不会引起死锁
  • Block 任务和 viewDidLoad 任务都要在主线程中执行,但是他们位于两个不同的串行队列中,而这两个任务不需要需要相互等待对方任务的完成就能继续进行,因此不会造成死锁。
问题四:分析下面代码是输出是什么?
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

  • 1 2 3 4 5
  • 问题的关键点在于并发队列,可以并发执行,即使是在同步的方式下进行,也不需要等待前面的任务,就不会造成死锁。
  • global_queue 如果换成自定义的串行队列,就产生了和问题一相同的死锁。
问题五:下面代码熟悉吗?
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"这是我们开发中最常用的写法");
    });
  • 这是我们开发中最常用的写法
  • 一般用于在其他子线程执行玩任务后,需要回到主线程进行 UI 绘制工作的时候用。
问题五:分析下面代码是输出是什么?
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        [self performSelector:@selector(printLog) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}

- (void)printLog {
    NSLog(@"2"); 
}
  • 这里其实考察的是对 performSelector: withObject: afterDelay: 方法的理解。打印 1 3
  • performSelector: withObject: afterDelay: 方法必须在有 runloop 的线程中调用,它需要把任务加到 runloop 中去,而全局并发队列中没有 runloop,所以,会直接失效。

9.2.dispatch_barrier_async()

问题一:怎么利用 GCD 实现多读单写?
流程分析
@implementation GlobalUserInfo

+ (GlobalUserInfo*) shareUserInfo {
    static GlobalUserInfo *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        concurrentQueue = dispatch_queue_create("userinfo_read_write", DISPATCH_QUEUE_CONCURRENT);
        userInfoDictionary = [[NSMutableDictionary alloc] init];
        // 假设对年龄这个数据的多读单写
        [userInfoDictionary setValue:@(1) forKey:@"age"];
    }
    return self;
}

- (int)getAge {
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrentQueue, ^{
        obj = [userInfoDictionary objectForKey:@"age"];
    });
    return [obj intValue];
}
- (void)setAge:(int)age {
    // 异步栅栏调用设置数据
    dispatch_barrier_async(concurrentQueue, ^{
        [self->userInfoDictionary setValue:@(age) forKey:@"age"];
        // 模拟耗时的写操作,才能看到效果
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"write queue4 %i", age);
    });
}
@end

9.3.dispatch_group_async()

问题一:怎么利用 GCD 实现这个需求:A、B、C 三个任务并发,完成后执行任务 D?
- (void)learnGCDGroup {
    // 模拟真实开发中多个数据资源需要下载
    NSMutableArray *urlArray = [[NSMutableArray alloc] init];
    for (int i = 0 ; i < 10; i++) {
        [urlArray addObject:@(i)];
    }
    
    // 创建一个group
    dispatch_group_t group = dispatch_group_create();
    
    // 创建一个并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("download_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 进行并发图片下载
    for (id obj in urlArray) {
        // 模拟耗时下载
        dispatch_group_async(group, concurrentQueue, ^{
            int randomTime = arc4random()% 3;
            [NSThread sleepForTimeInterval:randomTime];
            NSLog(@"download resource finished %@",obj);
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有资源已经下载完毕,可以再主线程更新 UI 了.");
    });
}

9.4.NSOperationQueue

问题一:哪些特点的方法使用 NSOperation 更为方便呢?
  • 添加任务依赖
  • 任务执行状态
  • 最大并发量
问题二:我们可以控制 NSOperation 的哪些状态?
  • isReady
  • isExecuting
  • isFinished
  • isCancelled
问题三:从代码的角度,我们如何控制 NSoperation 的状态?
  • 如果只重写了 main 方法,底层控制变更任务执行完成状态,以及任务退出。
  • 如果重写了 start 方法,自行控制任务状态。
问题四:系统是怎样移除一个 isFinished=YES 的 NSOperation 的?
  • 答案:通过 KVO

9.6.多线程 和 锁

问题一:在 iOS 当中有哪些锁呢?或者说你使用过哪些锁?
  • @synchoroinized(一般在创建单例对象的时候使用)
  • atomic(对被修饰对象进行原子操作,不负责使用
  • OSSpinLock(循环等待访问,不释放当前资源,用于轻量级数据访问,简单的 int 值+1/-1 操作)
  • NSRecursiveLock(适合在递归操作中使用)
  • NSLock
  • dispatch_semaphore_t

推荐阅读更多精彩内容