GCD队列管理之YYDispatchQueuePool

前言


iOS中,我们经常使用GCD来进行并发操作,我们并不需要关心线程的管理,Dispatch Queue会自动帮我们处理线程的创建和释放,在极大的简化并发操作的同时,某些情况下,Dispatch Queue的滥用可能会导致应用挂起,如向并发队列中添加阻塞的Block,阻塞的Block会导致系统创建更多的线程来处理任务,而GCD线程池的最大线程数为64个,所以一旦达到最大值,应用将挂起。
接下来,我将列出一些解决方案,来更好的使用Dispatch Queue

YYDispatchQueue


YYDispatchQueue的主要思想是使用串行队列来替换并发队列,可以为指定的NSQualityOfService创建一个队列池,由YYDispatchQueuePool对象来进行管理,每一种NSQualityOfService最多可以创建32个串行队列,通过- (dispatch_queue_t)queue;方法来获取可用队列,其采用Round Robin轮询算法。

除了可以创建队列池来管理并发外,还可以通过C的全局函数(dispatch_queue_t YYDispatchQueueGetForQOS(NSQualityOfService qos))来获取特定的NSQualityOfService串行队列,队列由全局的队列池来管理,每一种NSQualityOfService的串行队列数与核数相同,这样可以尽可能的减少线程之间的上下文切换。

YYDispatchQueuePool对象使用YYDispatchContext struct来管理队列池,代码目前还存在内存泄露的问题,因为struct是在堆上分配的内存,最后使用YYDispatchContextRelease释放context时只释放了结构体成员的内存空间,而没有释放结构体自己申请的空间,解决方法如下注释,加上free(context)即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct {
const char *name;
void **queues;
uint32_t queueCount;
int32_t counter;
} YYDispatchContext;

static void YYDispatchContextRelease(YYDispatchContext *context) {
if (!context) return;
if (context->queues) {
for (NSUInteger i = 0; i < context->queueCount; i++) {
void *queuePointer = context->queues[i];
dispatch_queue_t queue = (__bridge_transfer dispatch_queue_t)(queuePointer);
const char *name = dispatch_queue_get_label(queue);
if (name) strlen(name); // avoid compiler warning
queue = nil;
}
free(context->queues);
context->queues = NULL;
}
if (context->name) free((void *)context->name);
//内存泄露,需添加 free(context);
}

NSOperationQueue


使用NSOperationQueue,设置maxConcurrentOperationCount来控制并发量。

Dispatch Semaphores


使用Dispatch Queue时,可以用信号量来控制并发的数量,GCD提供信号量的支持,dispatch_semaphore_t用来表示信号量。在往队列添加任务之前,可以使用dispatch_semaphore_wait来获取信号量,成功获取后即可往队列中添加任务,当任务完成时,使用dispatch_semaphore_signal来释放信号量。

1
2
3
4
5
6
7
8
9
10
// 示例代码需要封装一下,不要直接在主线程或次级线程中直接调用dispatch_semaphore_wait,可能会引发UI挂起
...
dispatch_semaphore_t concurrencyLimitingSemaphore = dispatch_semaphore_create(limit);
...

dispatch_semaphore_wait(concurrencyLimitingSemaphore, DISPATCH_TIME_FOREVER);
dispatch_async(someConcurrentQueue, ^{
/* work goes here */
dispatch_semaphore_signal(concurrencyLimitingSemaphore);
}

附录


  1. https://stackoverflow.com/questions/7213845/number-of-threads-created-by-gcd
  2. https://github.com/ibireme/YYDispatchQueuePool
  3. https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW24