Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)
label参数指Dispatch Queue的名字,attr参数可以是指哪种Dispatch Queue。dispatch_get_global_queue(long identifier, unsigned long flags)
identifier是执行优先级的标识符,可取值为以下之一:DISPATCH_QUEUE_PRIORITY_HIGH,、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND,分别代表着优先级高、默认、低、后台。flags参数保留在未来使用。一般取0,如果非0可能返回NULL。(flags参数文档也是这么说的)。void dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
,queue指要执行的队列,block是要 执行的任务。void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block)
,queue指要执行的队列,block是要 执行的任务。代码示例
dispatch_queue_t serial_quque = dispatch_queue_create("test_serial_queue", NULL);//相当于DISPATCH_QUEUE_SERIAL
dispatch_queue_t concurrent_queue = dispatch_queue_create("test_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(serial_quque, ^{
NSLog(@"task1 in serial_queue");
[NSThread sleepForTimeInterval:10];//休眠个10秒
});
dispatch_async(serial_quque, ^{
NSLog(@"task2 in serial_quque");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"task1 in concurrent_queue");
[NSThread sleepForTimeInterval:10];//休眠个10秒
});
dispatch_async(concurrent_queue, ^{
NSLog(@"task2 in concurrent_queue");
});
运行结果:
2018-09-05 16:43:40.075903+0800 Test[12213:1549611] task1 in serial_queue
2018-09-05 16:43:40.076031+0800 Test[12213:1549613] task1 in concurrent_queue
2018-09-05 16:43:40.076433+0800 Test[12213:1549612] task2 in concurrent_queue
2018-09-05 16:43:50.080947+0800 Test[12213:1549611] task2 in serial_quque
task2 in serial_quque正好比task1 in serial_queue晚执行了10s,说明是同一个线程;
task1 in concurrent_queue和task1 in concurrent_queue可以认为几乎同时执行,说明是两个线程。
可以变更生成的Dispatch Queue的执行优先级。void dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue)
要变更优先级的Dispatch Queue,要使用相同优先级的Dispatch Queue。另外,dispatch_set_target_queue不仅可以变成优先级,还可以做成Dispatch Queue的执行阶层。比如在多个Serial Dispatch Queue中用dispatch_set_target_queue指定目标为某个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
代码示例
dispatch_queue_t serial_quque1 = dispatch_queue_create("test_serial_queue1", NULL);
dispatch_queue_t serial_quque2 = dispatch_queue_create("test_serial_queue2", NULL);
dispatch_queue_t serial_quque3 = dispatch_queue_create("test_serial_queue3", NULL);
dispatch_queue_t serial_quque10 = dispatch_queue_create("test_serial_queue10", NULL);
// dispatch_set_target_queue(serial_quque1, serial_quque10);
// dispatch_set_target_queue(serial_quque2, serial_quque10);
// dispatch_set_target_queue(serial_quque3, serial_quque10);
dispatch_async(serial_quque10, ^{
dispatch_async(serial_quque1, ^{
NSLog(@"-----serial_quque1");
NSThread *currentThread = [NSThread currentThread];
NSLog(@"-----thread1:%p",currentThread);
[NSThread sleepForTimeInterval:3];
});
dispatch_async(serial_quque2, ^{
NSLog(@"-----serial_quque2");
NSThread *currentThread = [NSThread currentThread];
NSLog(@"-----thread2:%p",currentThread);
[NSThread sleepForTimeInterval:3];
});
dispatch_async(serial_quque3, ^{
NSLog(@"-----serial_quque3");
NSThread *currentThread = [NSThread currentThread];
NSLog(@"-----thread3:%p",currentThread);
[NSThread sleepForTimeInterval:3];
});
});
运行结果:
2018-09-06 10:18:12.967948+0800 Test[12755:1620521] -----serial_quque1
2018-09-06 10:18:12.968003+0800 Test[12755:1620521] -----thread1:0x1c0271640
2018-09-06 10:18:12.968048+0800 Test[12755:1620522] -----serial_quque2
2018-09-06 10:18:12.968092+0800 Test[12755:1620520] -----serial_quque3
2018-09-06 10:18:12.968113+0800 Test[12755:1620520] -----thread3:0x1c02721c0
2018-09-06 10:18:12.968133+0800 Test[12755:1620522] -----thread2:0x1c4667400
结果显示分别开了三个线程,并行执行,我们再把上面的注释去掉,运行一遍,结果如下:
2018-09-06 10:18:44.578115+0800 Test[12759:1620999] -----serial_quque1
2018-09-06 10:18:44.578168+0800 Test[12759:1620999] -----thread1:0x1c0266340
2018-09-06 10:18:47.583390+0800 Test[12759:1620999] -----serial_quque2
2018-09-06 10:18:47.583461+0800 Test[12759:1620999] -----thread2:0x1c0266340
2018-09-06 10:18:50.588539+0800 Test[12759:1620999] -----serial_quque3
2018-09-06 10:18:50.588696+0800 Test[12759:1620999] -----thread3:0x1c0266340
可见,同时只能执行一个处理,变成串行处理了。
所以,在必须将不可执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并行执行。
可以实现延时执行处理。可以将指定的block追加到对应的Dispatch Queue中,主要代码如下:
dispatch_queue_t serial_quque1 = dispatch_queue_create("test_serial_queue1", NULL);
dispatch_time_t time= dispatch_time(DISPATCH_TIME_NOW, 2ull *NSEC_PER_SEC);
dispatch_after(time, serial_quque1, ^{
/*
*执行处理
*/
});
要注意的是,如果追加到Main Dispatch Queue在主线程的RunLoop中执行的话,在每隔1/60秒执行的RunLoop中,上述代码Block最快在2秒后执行处理,最慢在2+1/60秒后执行,并且Main Dispatch Queue有大量处理追加或主线程的处理,本身就有延迟,这个时间会更长。
dispatch_async是将指定的block非同步(异步)地追加到指定的Dispatch Queue中,dispatch_async不做任何等待就返回。而dispatch_sync就意着“同步”,在追加的block结束之前,dispatch_sync函数会一直在等待。
值得注意的是,dispatch_async容易造成死锁,例如在主线程执行以下代码就会发生死锁:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello");
});
上述代码中dispatch_sync在等待返回结果,造成主线程在等待,而输出hello的block也是在主线程中执行,这个block也在等待主线程执行,从而造成死锁。
除了只在主线程造成死锁以外,两个线程也可以造成死锁:
dispatch_queue_t serial_quque = dispatch_queue_create("test_serial_queue", NULL);
dispatch_async(serial_quque, ^{
int i = 0;
while (i < 2) {
i++;
i--;
}
});
NSLog(@"before hello");
dispatch_sync(serial_quque, ^{
NSLog(@"hello");
});
NSLog(@"after hello");
会发现控制台只输出了一句before hello,就再也没输出了,serial_quque所对应的线程一直在执行dispatch_async追加的block,dispatch_sync追加的bock也都在等待该线程结束,而主线程就一直等待dispatch_sync返回结果,所以造成死锁。
当使用多个Serial Dispatch Queue或者使用concurrent Dispatch Queue并行执行多个处理时,我们知道,这些处理的顺序是不定的,所有处理都执行完的时间也是不定的,如果我们想在所有处理结束以后执行某个处理,这时就可以用Dispatch Group。
代码示例
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All is done.");
});
使用dispatch_group_create函数生成一个Dispatch Group,使用dispatch_group_notify函数在group的所有处理都执行完后通知某个线程执行某个处理。与dispatch_async追加的block属于queue不同的是,dispatch_group_async追加的block是属于group的。
另外,还可以用dispatch_group_wait来指定等待的时间来观察group是否已经执行完:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC));
if (result == 0) {
//group已经执行完所有的处理
}else{
//group还未执行完所有的处理
}
dispatch_group_wait第二个参数指定等待的时间,当使用DISPATCH_TIME_FOREVER时,dispatch_group_wait会在当前线程一直等到到group执行完了才有返回结果,并且返回结果恒为0。当dispatch_group_wait返回值为0时代表group已经执行完所有处理,非0时则未执行完所有处理。
值得注意的是 ,dispatch_group_wait会造成调用该函数的线程(当前线程)停止,等待dispatch_group_wait返回结果,存在死锁的发生,所以还是推荐使用dispatch_group_notify。
dispatch_barrier_async起到了一种在并行的多线程处理中再插入隔离的操作,该隔离操作是在插入之前的并行操作执行完以后再执行,当该隔离操作执行完了以后后续的并行处理可以继续并行执行。
比如文件或者数据的读和写,对于读操作,可以并行处理,毕竟内容都一样,但如果在读的同时要执行写操作的话,就会造成读取的数据不符或数据竞争,所以应该使用dispatch_barrier_async进行写操作:
dispatch_queue_t quque = dispatch_queue_create("test_serial_queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, block1_reading);
dispatch_async(quque, block2_reading);
dispatch_async(quque, block3_reading);
dispatch_async(quque, block4_reading);
dispatch_barrier_async(quque, block_writing);
dispatch_async(queue, block5_reading);
dispatch_async(quque, block6_reading);
dispatch_async(quque, block7_reading);
dispatch_async(quque, block8_reading);
所以,通过dispatch_barrier_async隔离开写操作,保证数据安全。
Dispatch Semaphore也可以实现并行处理的线程等待,主要通过信号量来控制,相关API的说明有点绕,需要仔细分析。
1. dispatch_semaphore_create函数可以创建一个信号量,只有一个参数value,一般传一个0就可以在两个线程里调度,如果需要管理有限的资源池,那么资源的数量就是value的值。
2. dispatch_semaphore_signal函数:参数是一个dispatch_semaphore_t类型(关联的信号量),调用该函数会使得信号量加1。
3. dispatch_semaphore_wait函数:第一个参数是dispatch_semaphore_t类型(关联的信号量);第二个参数是dispatch_time_t类型,该参数表示当前线程等待信号量的时间。调用该函数会使信号量减1,如果信号量小于0,则会在返回结果之前使当前线程一直处于等待状态,那么何时返回结果呢?就是经过了由第二参数timeout定义的时间。一旦第二参数取值DISPATCH_TIME_FOREVER,就意味着当前线程一直等待(阻塞线程),等到信号量大于或等于0才会唤醒。
代码示例
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
以上代码会导致当前线程一直等到,因为信号量在dispatch_semaphore_wait以后一直都是小于0。
再来:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create("test_queue1", NULL), ^{
NSLog(@"in test queue1");
NSLog(@"sleep for 2s in test queue1");
[NSThread sleepForTimeInterval:2];
NSLog(@"sleeping finished in test queue1");
dispatch_semaphore_signal(semaphore);//信号量加1
});
dispatch_async(dispatch_queue_create("test_queue2", NULL), ^{
NSLog(@"in test queue2");
NSLog(@"sleep for 3s in test queue2");
[NSThread sleepForTimeInterval:3];
NSLog(@"sleeping finished in test queue2");
dispatch_semaphore_signal(semaphore);//信号量加1
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//信号量减1,如果减1后还是小于0,就继续等待信号量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"waiting finished");
运行结果:
2018-09-07 09:51:58.971196+0800 Test[4268:862931] in test queue1
2018-09-07 09:51:58.971199+0800 Test[4268:862930] in test queue2
2018-09-07 09:51:58.971248+0800 Test[4268:862931] sleep for 2s in test queue1
2018-09-07 09:51:58.971263+0800 Test[4268:862930] sleep for 3s in test queue2
2018-09-07 09:52:00.971804+0800 Test[4268:862931] sleeping finished in test queue1
2018-09-07 09:52:01.976477+0800 Test[4268:862930] sleeping finished in test queue2
2018-09-07 09:52:01.976733+0800 Test[4268:862851] waiting finished
可见,当前线程还是会等待test_queue2对应的线程休眠完3秒才会结束等待。
虽然Dispatch Group和dispatch_barrier_async也可以实现多线程调度和通信,但Dispatch Semaphore却可以实现粒度更小的排他控制。