美文网首页
iOS 中使用的那些多线程与同步机制

iOS 中使用的那些多线程与同步机制

作者: Jseanj86 | 来源:发表于2017-02-14 15:21 被阅读0次
这里分析的开源库及版本
AFNetworking:3.0
SDWebImage:4.0

一. AFURLSessionManager 中的 completionQueuecompletionGroup
在使用时,可以通过外部传入,也可以直接使用框架提供的group私有单例或者主队列。

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
    if (self.completionHandler) {
        self.completionHandler(task.response, responseObject, error);
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
    });
});

这段代码是在 URLSession:task:didCompleteWithError 中执行的,意思是将自定义的完成回调放在 completionQueue 上执行。由于要发已完成的通知,所以放在主线程上执行发通知的操作。
这里并没有使用 dispatch_group_notifydispatch_group_wait 来同步,那么使用 group 的意义在哪呢?一开始我也不太明白,这里有人给出了见解,但是外部使用者如果认为 completionGroup 属性可读,而直接使用它来通知也会出现问题,因为内部使用的是私有单例,所以这块为啥用 group 还是有些困惑的。

二. AFURLSessionManager 中的 url_session_manager_processing_queue

dispatch_async(url_session_manager_processing_queue(), ^{
    NSError *serializationError = nil;
    responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
    ...
});

url_session_manager_processing_queue 是框架提供的自定义并发队列单例,这段代码也是在 URLSession:task:didCompleteWithError 中执行的,意思是将反序列化的操作放在并发队列上执行。

三. dispatch_semaphore_t

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

使用信号量来同步读取。其实也可以用 dispatch_sync

四. NSLock

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

由于 mutableTaskDelegatesKeyedByTaskIdentifier 是字典,多线程读取字典要加锁。其实也可以用 dispatch_barrier_async

五. synchronized
SDWebImageManager 中的 failedURLs 是一个集合。

@synchronized (self.failedURLs) {
    isFailedUrl = [self.failedURLs containsObject:url];
}
@synchronized (self.failedURLs) {
    [self.failedURLs addObject:url];
}
@synchronized (self.failedURLs) {
    [self.failedURLs removeObject:url];
}

这个同步机制没什么可说的。

六. 读写操作
AFHTTPRequestSerializer 中的 requestHeaderModificationQueue 是个并发队列,mutableHTTPRequestHeaders 是个字典,针对字典同时读是可以的,但是同时写或者同时读写是不可以的。

- (NSDictionary *)HTTPRequestHeaders {
    NSDictionary __block *value;
    dispatch_sync(self.requestHeaderModificationQueue, ^{
        value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
    });
    return value;
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    NSString __block *value;
    dispatch_sync(self.requestHeaderModificationQueue, ^{
        value = [self.mutableHTTPRequestHeaders valueForKey:field];
    });
    return value;
}

这两个操作都是字典的读操作,是可以同时读的。

- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
        [self.mutableHTTPRequestHeaders setValue:value forKey:field];
    });
}
- (void)clearAuthorizationHeader {
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
        [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
    });
}

这两个操作都是字典的写操作,必须与其他操作互斥。

再来看 AFAutoPurgingImageCache 中的例子,synchronizationQueue 是一个并发队列,cachedImages 是字典,currentMemoryUsage 是整数。

- (UInt64)memoryUsage {
    __block UInt64 result = 0;
    dispatch_sync(self.synchronizationQueue, ^{
        result = self.currentMemoryUsage;
    });
    return result;
}
- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
    __block UIImage *image = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        AFCachedImage *cachedImage = self.cachedImages[identifier];
        image = [cachedImage accessImage];
    });
    return image;
}

这两个操作都是读操作,是可以同时读的。

- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
    dispatch_barrier_async(self.synchronizationQueue, ^{
        AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];

        AFCachedImage *previousCachedImage = self.cachedImages[identifier];
        if (previousCachedImage != nil) {
            self.currentMemoryUsage -= previousCachedImage.totalBytes;
        }

        self.cachedImages[identifier] = cacheImage;
        self.currentMemoryUsage += cacheImage.totalBytes;
    });
    ...
}
- (BOOL)removeImageWithIdentifier:(NSString *)identifier {
    __block BOOL removed = NO;
    dispatch_barrier_sync(self.synchronizationQueue, ^{
        AFCachedImage *cachedImage = self.cachedImages[identifier];
        if (cachedImage != nil) {
            [self.cachedImages removeObjectForKey:identifier];
            self.currentMemoryUsage -= cachedImage.totalBytes;
            removed = YES;
        }
    });
    return removed;
}

这两个操作都是字典写操作,必须与其他操作互斥。注意这里如果当前线程需要使用 block 中返回的数据,需要使用 dispatch_barrier_sync

在看 SDWebImageDownloaderOperation 中的例子,同样,barrierQueue 是并发队列,callbackBlocks 是数组。

- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
    __block NSMutableArray<id> *callbacks = nil;
    dispatch_sync(self.barrierQueue, ^{
        callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
        [callbacks removeObjectIdenticalTo:[NSNull null]];
    });
    return [callbacks copy];    // strip mutability here
}

这个操作是字典的读操作。

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    dispatch_barrier_async(self.barrierQueue, ^{
        [self.callbackBlocks addObject:callbacks];
    });
    return callbacks;
}
- (BOOL)cancel:(nullable id)token {
    __block BOOL shouldCancel = NO;
    dispatch_barrier_sync(self.barrierQueue, ^{
        [self.callbackBlocks removeObjectIdenticalTo:token];
        if (self.callbackBlocks.count == 0) {
            shouldCancel = YES;
        }
    });
    if (shouldCancel) {
        [self cancel];
    }
    return shouldCancel;
}

这两个操作都是字典写操作,必须与其他操作互斥。

总结

  1. 什么时候用sync、什么时候用async?
    如果当前线程后续的操作需要用到 block 中的数据时,就用sync,否则用async。

  2. 什么时候用串行队列、什么时候用并发队列?
    如果 block 中的操作需要互斥的进行(比如写字典、文件IO),就用串行队列,否则用并发队列。

PS:
同步机制还包括 NSRecursiveLockNSConditionLockNSConditionOSSpinLockpthread_mutex等,后续会单独出一篇分析锁的文章。

相关文章

  • OC--各种线程锁

    参考:正确使用多线程同步锁@synchronized()iOS中的锁iOS多线程安全详解iOS 常见知识点(三):...

  • 线程锁

    探讨iOS开发中各种锁使用NSCondition实现多线程同步 NSCondition是线程同步, 阻塞线程。 取...

  • iOS多线程--并行开发二

    接上文iOS多线程--并行开发一 4、线程同步 说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程...

  • iOS 中使用的那些多线程与同步机制

    一. AFURLSessionManager 中的 completionQueue 和 completionGro...

  • iOS复习之多线程

    关于iOS多线程,你看我就够了iOS多线程--彻底学会多线程使用dispatch_group来进行线程同步 iOS...

  • iOS 多线程基础

    转自:iOS 多线程基础 - 简书 多线程同步和异步的区别?IOS中如何实现多线程的同步? 异步:可以同时执行多条...

  • ios知识点(9)多线程

    iOS多线程GCD详解 使用GCD iOS多线程中,队列和执行的排列组合结果分析 存在一点小瑕疵,如果同步(syn...

  • iOS面试题06-应用程序(★★)

    《2018 iOS面试题系列》 一、NSRunLoop的实现机制,及在多线程中如何使用 NSRunLoop是IOS...

  • 起底多线程同步锁(iOS)

    起底多线程同步锁(iOS) 起底多线程同步锁(iOS)

  • iOS面试题整理(三)

    1、NSRunLoop的实现机制,及在多线程中如何使用? NSRunLoop是IOS消息机制的处理模式 1.NSR...

网友评论

      本文标题:iOS 中使用的那些多线程与同步机制

      本文链接:https://www.haomeiwen.com/subject/jxpxwttx.html