信号量

并行执行任务时更新数据,会产生数据不一致的情况,还可能导致异常。虽然使用Serial Dispatch Queuedispatch_barrier_async函数可以避免。但有必要进行细粒度的排他控制。

信号量内部有一个可以原子递增或者递减的值,如果一个任务尝试减少信号量的值,使其等于0,那么这个任务将被等待。直到其他的任务(其他的线程中)增加信号量的值。

通常一个信号量被初始化一个最大值,表示资源可以同时访问的最大数。

1
2
3
4
5
6
7
8
9
10
11
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
for (NSUInteger index = 0; index < 1000; index++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:@(index)];
dispatch_semaphore_signal(semaphore);
});
}

使用dispatch_semaphore_create生成一个dispatch_semaphore_t类型的Dispatch Semaphore。

开始的时候初始化为1。

dispatch_semaphore_wait等待,一直到Dispatch Semaphore的计数值大于等于1。并且会减1后返回,下一个并发任务进行访问的时候,就刚好为0,需要等待。直到前一个任务执行完成后,进行dispatch_semaphore_signal,对计数值进行加1。那么这时候计数值又大于1了,下一个并发任务就可以执行了。

源码分析

AFNetworing框架的AFURLSessionManager中的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (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 Semaphore 初始化就是0,所以dispatch_semaphore_wait会一直等待,直到任务中调用dispatch_semaphore_signal。

最佳实践

信号量是底层工具,它还是很强大的,但是信号量本身就是锁,能不用锁就不要用。有些时候需要把异步任务转换成同步任务时,信号量就非常适合,例如 AFNetworing的使用。

坚持原创技术分享,您的支持将鼓励我继续创作!