1. 什么是 GCD
GCD
(Grand Central Dispatch)是苹果公司为多核的并行运算提出的解决方案,它是一套纯C语言实现的API。使用GCD分派队列(dispath queue)
可以同步或者异步地执行任务,以及串行或者并发的执行任务。GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程),所以开发人员只需要告诉GCD想要执行什么任务,而不需要编写任何管理线程的代码。[GCD源码]
2. 相关名词解释
2.1 同步和异步
同步和异步主要体现在能不能开启新的线程,以及会不会阻塞当前线程。
同步
:在当前线程中执行任务,不具备开启新线程的能力。同步任务会阻塞当前线程。异步
:在新的线程中执行任务,具备开启新线程的能力。并不一定会开启新线程,比如在主队列中通过异步执行任务并不会开启新线程。异步任务不会阻塞当前线程。
2.2 串行和并发
串行和并发主要影响任务的执行方式。
串行
:一个任务执行完毕后,再执行下一个任务。并发
:多个任务并发(同时)执行。
注意:
如果当前队列是串行队列,通过同步函数向当前队列中添加任务会造成死锁。(串行队列中添加异步任务和并发队列中添加同步任务都不会死锁。)
3. GCD如何执行任务
任务就是要执行什么操作,GCD中任务是放在block中(dispatch_block_t
)或者函数中(dispatch_function_t
)的(比较常见的是放在block中)。任务的执行方式有同步
和异步
2种。GCD中的同步函数和异步函数如下:
//同步执行
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
这里我们先通过dispatch_async
和dispatch_async_f
这两个异步函数来了解block任务和函数任务的使用方法,其它函数后面会单独拿出来介绍。这里要说明的是,block任务和函数任务功能是一样的,所以后面内容都以block任务来做讲解
。
3.1 block任务(dispatch_block_t)的使用方法
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 参数一(queue):派发队列,将任务添加到这个队列中。
- 参数二(block):执行任务的block。
- (void)blockTask{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(dispatch_get_global_queue(0, 0), ^{ // blcok任务
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时任务
NSLog(@"block任务--%@",[NSThread currentThread]);
});
}
// ****************运行结果****************
2019-12-28 18:58:08.194661+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:58:09.205977+0800 MultithreadingDemo[21777:4006340] block任务--<NSThread: 0x600001214bc0>{number = 3, name = (null)}
3.2 函数任务(dispatch_function_t)的使用方法
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
- 参数一(queue):派发队列,将任务添加到这个队列中。
- 参数三(work):执行任务的函数,
dispatch_function_t
是一个函数指针(其定义为:typedef void (*dispatch_function_t)(void *_Nullable);
),指向耗时任务所在的的函数。 - 参数二(context):是给耗时任务的函数传的参数,参数类型是任意类型。
- (void)functionTask{
NSLog(@"%@",[NSThread currentThread]);
dispatch_async_f(dispatch_get_global_queue(0, 0), @"abc124", testFunction);
}
// 任务所在的函数
void testFunction(void *para){
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时任务
NSLog(@"函数任务参数:%@----线程:%@",para,[NSThread currentThread]);
}
// ****************运行结果****************
2019-12-28 18:51:01.711140+0800 MultithreadingDemo[21777:4006228] <NSThread: 0x60000124a200>{number = 1, name = main}
2019-12-28 18:51:02.716100+0800 MultithreadingDemo[21777:4006340] 函数任务参数:abc124----线程:<NSThread: 0x600001214bc0>{number = 3, name = (null)}
4. GCD的队列(dispatch_queue)
GCD的派发队列(dispatch_queue)
用来存放任务(dispatch_block
)并控制任务的执行方式。(dispatch_queue)
采用的是FIFO
(先进先出)的原则,也就是说先提交的任务会先安排执行(先安排并不意味着先执行完)。
4.1 队列的类型
GCD的队列可以分为2大类型:串行队列
和并发队列
。
串行队列(Serial Dispatch Queue)
:让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)。并发队列(Concurrent Dispatch Queue)
:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)。要注意的是并发功能只有在异步(dispatch_async)函数下才有效。并发数是有上限的
,并发执行的任务数量取决于当前系统的状态。
4.2 如何获取队列
4.2.1 主队列
主队列由系统自动创建,并与应用程序的主线程相关联。主队列上的任务一定是在主线程上执行(不过主线程并不是只执行主队列的任务,为了避免线程切换对性能的消耗,主线程还有可能会执行其他队列的任务)。
主队列是一个串行队列,所以即便是在异步函数中也不会去开启新的线程。它只有在主线程空闲的时候才能调度里面的任务。
开发中一般是子线程执行完耗时操作后,我们获取到主队列并将刷新UI的任务添加到主队列中去执行。
// 主队列同步函数
- (void)mainQueueTest1{
NSLog(@"主队列同步函数");
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(mainQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
}
// ****************运行结果****************
------线程堵塞------
上面程序运行会堵塞线程,因为主队列是串行队列,如果通过同步函数向主队列中添加任务,那么并不会开启子线程来执行这个任务,而是在主线程中执行。我们将方法mainQueueTest1
称作task1,将同步函数添加的任务称作task2。task1和task2都在主队列中,主队列先安排task1到主线程执行,等task1执行了完了才能安排task2到主线程执行,而task1又必须等task2执行完了才能继续往下执行,而task2又必须等task1执行完了才能被主队列安排执行,这样就造成了相互等待而卡死(死锁)。
所以只要当前队列是串行队列,通过同步函数往当前队列添加任务都会造成死锁。
// 主队列异步函数
- (void)mainQueueTest2{
NSLog(@"主队列异步函数");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(mainQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************运行结果****************
2019-12-28 15:42:04.622806+0800 MultithreadingDemo[20363:3934000] 主队列异步函数
2019-12-28 15:42:04.623171+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:42:05.625086+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:06.625727+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:07.627400+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:08.628809+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:09.629977+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
在主队列中通过异步函数添加的任务都是在主线程执行(因为主队列中的任务都在主线程执行,所以并不会开启新的线程),并且是串行执行。
4.2.2 获取全局队列
获取全局队列的函数是dispatch_get_global_queue(long identifier, unsigned long flags)
。第一个参数表示队列的优先级(优先级等级如下表所示);第二个参数是保留参数,传0即可。全局队列是一个并发队列
。
队列优先级
优先级 | 描述 |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | 最高优先级 |
DISPATCH_QUEUE_PRIORITY_DEFAULT | 默认优先级 |
DISPATCH_QUEUE_PRIORITY_LOW | 低优先级 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | 后台优先级(表示用户不需要知道任务什么时候完成,选这项速度会非常慢) |
注意
这里的优先级和NSOperation
中的优先级不同,这里的优先级是指队列的优先级,NSOperation
中的优先级是指任务的优先级。
对于全局队列,如果两个参数一样,那么获取的是同一个队列,如下所示queue1和queue2是同一个队列
。
// 打印两个队列的地址发现是同一个队列
dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
// 全局队列同步函数
- (void)globalQueueTest1{
NSLog(@"全局队列同步");
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(globalQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 15:42:41.301152+0800 MultithreadingDemo[20363:3934000] 全局队列同步
2019-12-28 15:42:42.302694+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:43.304170+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:44.305796+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:45.307420+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309029+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:42:46.309392+0800 MultithreadingDemo[20363:3934000] ----end----
可见通过同步函数向全局队列添加的任务都是在当前线程执行(本例中当前线程是主线程),并且是串行执行。
// 全局队列异步函数
- (void)globalQueueTest2{
NSLog(@"全局队列异步");
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(globalQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 15:43:44.399828+0800 MultithreadingDemo[20363:3934000] 全局队列异步
2019-12-28 15:43:44.400040+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:43:45.405629+0800 MultithreadingDemo[20363:3936377] 0--<NSThread: 0x600003065740>{number = 5, name = (null)}
2019-12-28 15:43:45.405655+0800 MultithreadingDemo[20363:3936378] 2--<NSThread: 0x60000306b100>{number = 9, name = (null)}
2019-12-28 15:43:45.405722+0800 MultithreadingDemo[20363:3934072] 1--<NSThread: 0x60000306b080>{number = 6, name = (null)}
2019-12-28 15:43:45.405657+0800 MultithreadingDemo[20363:3936380] 4--<NSThread: 0x60000306cb40>{number = 7, name = (null)}
2019-12-28 15:43:45.405779+0800 MultithreadingDemo[20363:3936379] 3--<NSThread: 0x600003061780>{number = 8, name = (null)}
可见通过异步函数向全局队列添加的任务不是在当前线程执行,而是多个任务是在不同的线程中执行,并且是并发执行。
4.2.3 自己创建队列
自己创建队列的方法如下,可以创建串行队列
和并发队列
两种队列。
DISPATCH_QUEUE_SERIAL
:串行队列,队列中的任务按先进先出的顺序连续执行(一个任务执行完了才能执行下一个任务)。DISPATCH_QUEUE_SERIAL_INACTIVE
:串行队列,此时这个串行队列的状态是不活跃的,在这个串行队列调用之前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及以后的系统)。DISPATCH_QUEUE_CONCURRENT
:并发队列,队列中的任务可以并发(同时)执行。DISPATCH_QUEUE_CONCURRENT_INACTIVE
:并发队列,此时这个并发队列的状态是不活跃的,在这个并发队列调用之前,必须使用dispatch_activate()函数激活队列(仅支持ios 10.0及以后的系统)。
/**
参数1:队列的名称,方便调试
参数2:队列的类型,
如果是串行队列,参数2为DISPATCH_QUEUE_SERIAL或者NULL
如果是并发队列,参数2为DISPATCH_QUEUE_CONCURRENT
*/
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
对于自己创建的队列,如果两个参数一样,那么创建的是两个不同的队列,如下所示queue1和queue2是不同的队列
。
// 打印两个队列的地址发现是不同的队列
dispatch_queue_t queue1 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("abc", DISPATCH_QUEUE_SERIAL);
// 自建并串行列同步函数
- (void)customQueueTest1{
NSLog(@"自建串行队列同步函数");
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 15:45:26.206409+0800 MultithreadingDemo[20363:3934000] 自建串行队列同步函数
2019-12-28 15:45:27.207582+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:28.208068+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:29.208996+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:30.210593+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211514+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:45:31.211855+0800 MultithreadingDemo[20363:3934000] ----end----
可见通过同步函数向自己创建的串行队列中添加的任务都是在当前线程执行,不会开启新的线程,而且是串行执行。
注意这个和通过同步函数向主队列添加任务不同。我们还是将方法customQueueTest1
称作task1,将同步函数添加的任务称作task2。这里两个task属于2个不同的队列,task1由主队列安排执行,task2由自己创建的队列来安排执行。首先主队列安排task1到主线程中执行,当执行到task2的地方时,由自己创建的队列安排task2到主线程中执行(无需等待task1完成),等task2执行完后继续执行task1,所以这里不会造成线程堵塞。
// 自建串行队列异步函数
- (void)customQueueTest2{
NSLog(@"自建串行队列异步函数");
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(serialQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 15:52:55.958177+0800 MultithreadingDemo[20363:3934000] 自建串行队列异步函数
2019-12-28 15:52:55.958379+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 15:52:56.962162+0800 MultithreadingDemo[20363:3937135] 0--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:57.966949+0800 MultithreadingDemo[20363:3937135] 1--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:58.971743+0800 MultithreadingDemo[20363:3937135] 2--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:52:59.977245+0800 MultithreadingDemo[20363:3937135] 3--<NSThread: 0x60000306c780>{number = 10, name = (null)}
2019-12-28 15:53:00.981966+0800 MultithreadingDemo[20363:3937135] 4--<NSThread: 0x60000306c780>{number = 10, name = (null)}
可见通过异步函数向自己创建的串行队列中添加的任务是在新开启的线程中执行,而且所有任务都是在同一个子线程中执行(也就是说多个任务只会开启一个子线程),而且是串行执行。
// 自建并发队列同步函数
- (void)customQueueTest3{
NSLog(@"自建并发队列同步函数<##>");
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 5; i++) {
dispatch_sync(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 15:56:47.003113+0800 MultithreadingDemo[20363:3934000] 自建并发队列同步函数
2019-12-28 15:56:48.003737+0800 MultithreadingDemo[20363:3934000] 0--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:49.005293+0800 MultithreadingDemo[20363:3934000] 1--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:50.006181+0800 MultithreadingDemo[20363:3934000] 2--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:51.006946+0800 MultithreadingDemo[20363:3934000] 3--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007620+0800 MultithreadingDemo[20363:3934000] 4--<NSThread: 0x60000300d040>{number = 1, name = main}
2019-12-28 15:56:52.007953+0800 MultithreadingDemo[20363:3934000] ----end----
可见通过同步函数向自己创建的并发队列中添加的任务是在当前线程中执行,而且是串行执行。
// 自建并发队列异步函数
- (void)customQueueTest4{
NSLog(@"自建并发队列异步函数<##>");
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.myQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 5; i++) {
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1.0f]; // 模拟耗时操作
NSLog(@"%ld--%@",i,[NSThread currentThread]);
});
}
NSLog(@"----end----");
}
// ****************打印结果****************
2019-12-28 16:01:01.262912+0800 MultithreadingDemo[20363:3934000] 自建并发队列异步函数<##>
2019-12-28 16:01:01.263306+0800 MultithreadingDemo[20363:3934000] ----end----
2019-12-28 16:01:02.265466+0800 MultithreadingDemo[20363:3942614] 1--<NSThread: 0x600003061700>{number = 15, name = (null)}
2019-12-28 16:01:02.265478+0800 MultithreadingDemo[20363:3944671] 0--<NSThread: 0x60000306cec0>{number = 17, name = (null)}
2019-12-28 16:01:02.265608+0800 MultithreadingDemo[20363:3944674] 4--<NSThread: 0x600003061a80>{number = 19, name = (null)}
2019-12-28 16:01:02.265610+0800 MultithreadingDemo[20363:3944673] 3--<NSThread: 0x600003065740>{number = 18, name = (null)}
2019-12-28 16:01:02.265622+0800 MultithreadingDemo[20363:3944672] 2--<NSThread: 0x600003061640>{number = 20, name = (null)}
可见通过异步函数向自己创建的并发队列添加的任务不是在当前线程执行,而是多个任务是在不同的线程中执行,并且是并发执行。
小结:
同步 | 异步 | |
---|---|---|
主队列 | 没有开启新线程;线程阻塞 | 没有开启新线程;串行执行任务 |
全局队列 | 没有开启新线程;串行执行任务 | 有开启新线程;并发执行任务 |
自己创建串行队列 | 没有开启新线程;串行执行任务 | 有开启新线程;串行执行任务 |
自己创建并发队列 | 没有开启新线程;串行执行任务 | 有开启新线程;并发执行任务 |
4.3 队列的挂起与与恢复
当一个派发队列中有任务还没被安排执行时,我们可以选择将队列挂起,然后在合适的时机恢复执行。挂起和恢复的行数如下:
//挂起指定的dispatch_queue
void dispatch_suspend(dispatch_object_t object);
//恢复指定的dispatch_queue
void dispatch_resume(dispatch_object_t object);
注意dispatch_suspend
只能挂起还没执行的任务,已经执行和正在执行的任务是没有影响的。而且对主队列
执行挂起操作是无效的。
5. 栅栏函数(dispatch_barrier)
栅栏函数是GCD提供的用于阻塞分割任务的一组函数。其主要作用就是在队列中设置栅栏,来人为干预队列中任务的执行顺序。也可以理解为用来设置任务之间的依赖关系。栅栏函数分同步栅栏函数
和异步栅栏函数
:
//同步栅栏函数
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
//异步栅栏函数
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
同步栅栏函数和异步栅栏函数的异同点:
相同点
:它们都将多个任务分割成了3个部分,第一个部分是栅栏函数之前的任务,是最先执行的;第二个部分是栅栏函数添加的任务,这个任务要等栅栏函数之前的任务都执行完了才会执行;第三个部分是栅栏函数之后的任务,这个部分要等栅栏函数里面的任务执行完了才会执行。不同点
:同步栅栏函数不会开启新线程,其添加的任务在当前线程执行,会阻塞当前线程;异步栅栏函数会开启新线程来执行其添加的任务,不会阻塞当前线程。
假设我现在有这样一个需求:
一个大文件被分成part1和part2两部分存在服务器上,现在要将part1和part2都下载下来后然后合并并写入磁盘。这里其实有4个任务,下载part1是task1,下载part2是task2,合并part1和part2是task3,将合并后的文件写入磁盘是task4。这4个任务执行顺序是task1和task2并发异步执行,这两个任务都执行完了后再执行task3,task3执行完了再执行task4。
通过同步栅栏函数和异步栅栏函数都可以实现这个需求,下面我们来看看用同步和异步栅栏函数来实现有什么区别。
5.1 同步栅栏函数(dispatch_barrier_sync)
// 同步栅栏函数
- (void)syncBarrier{
NSLog(@"当前线程1");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始下载part1---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0f]; // 模拟下载耗时2s
NSLog(@"完成下载part1---%@",[NSThread currentThread]);
});
NSLog(@"当前线程2");
dispatch_async(queue, ^{
NSLog(@"开始下载part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成下载part2---%@",[NSThread currentThread]);
});
NSLog(@"当前线程3");
dispatch_barrier_sync(queue, ^{
NSLog(@"开始合并part1和part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成合并part1和part2---%@",[NSThread currentThread]);
});
NSLog(@"当前线程4");
dispatch_async(queue, ^{
NSLog(@"开始写入磁盘---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成写入磁盘---%@",[NSThread currentThread]);
});
NSLog(@"当前线程5");
}
// ****************打印结果****************
2019-12-29 11:48:52.804380+0800 MultithreadingDemo[28408:4279520] 当前线程1
2019-12-29 11:48:52.805902+0800 MultithreadingDemo[28408:4279520] 当前线程2
2019-12-29 11:48:52.806323+0800 MultithreadingDemo[28408:4279520] 当前线程3
2019-12-29 11:48:52.806282+0800 MultithreadingDemo[28408:4292323] 开始下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:52.806513+0800 MultithreadingDemo[28408:4280937] 开始下载part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:53.806988+0800 MultithreadingDemo[28408:4280937] 完成下载part2---<NSThread: 0x6000030d0600>{number = 9, name = (null)}
2019-12-29 11:48:54.809914+0800 MultithreadingDemo[28408:4292323] 完成下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:54.810426+0800 MultithreadingDemo[28408:4279520] 开始合并part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.810924+0800 MultithreadingDemo[28408:4279520] 完成合并part1和part2---<NSThread: 0x6000030a5940>{number = 1, name = main}
2019-12-29 11:48:55.811204+0800 MultithreadingDemo[28408:4279520] 当前线程4
2019-12-29 11:48:55.811405+0800 MultithreadingDemo[28408:4279520] 当前线程5
2019-12-29 11:48:55.811455+0800 MultithreadingDemo[28408:4292323] 开始写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:48:56.816247+0800 MultithreadingDemo[28408:4292323] 完成写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
运行结果可以看出,需求的功能是实现了,但是有个问题,同步栅栏函数在分割任务的同时也阻塞了当前线程,这里当前线程是主线程,这就意味着在task1、task2和task3这3个任务都完成之前,UI界面是出于卡死状态的,这种用户体验显然是非常糟糕的。下面我们来看看异步栅栏函数来实现这个功能的效果。
5.2 异步栅栏函数(dispatch_barrier_async)
// 异步栅栏函数
- (void)asyncBarrier{
NSLog(@"当前线程1");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始下载part1---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0f]; // 模拟下载耗时2s
NSLog(@"完成下载part1---%@",[NSThread currentThread]);
});
NSLog(@"当前线程2");
dispatch_async(queue, ^{
NSLog(@"开始下载part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成下载part2---%@",[NSThread currentThread]);
});
NSLog(@"当前线程3");
dispatch_barrier_async(queue, ^{
NSLog(@"开始合并part1和part2---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成合并part1和part2---%@",[NSThread currentThread]);
});
NSLog(@"当前线程4");
dispatch_async(queue, ^{
NSLog(@"开始写入磁盘---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"完成写入磁盘---%@",[NSThread currentThread]);
});
NSLog(@"当前线程5");
}
// ****************打印结果****************
2019-12-29 11:50:08.465656+0800 MultithreadingDemo[28408:4279520] 当前线程1
2019-12-29 11:50:08.466085+0800 MultithreadingDemo[28408:4279520] 当前线程2
2019-12-29 11:50:08.466158+0800 MultithreadingDemo[28408:4292323] 开始下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:08.466392+0800 MultithreadingDemo[28408:4279520] 当前线程3
2019-12-29 11:50:08.466516+0800 MultithreadingDemo[28408:4293001] 开始下载part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:08.466586+0800 MultithreadingDemo[28408:4279520] 当前线程4
2019-12-29 11:50:08.466707+0800 MultithreadingDemo[28408:4279520] 当前线程5
2019-12-29 11:50:09.471031+0800 MultithreadingDemo[28408:4293001] 完成下载part2---<NSThread: 0x6000030d0440>{number = 14, name = (null)}
2019-12-29 11:50:10.469408+0800 MultithreadingDemo[28408:4292323] 完成下载part1---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:10.469996+0800 MultithreadingDemo[28408:4292323] 开始合并part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475249+0800 MultithreadingDemo[28408:4292323] 完成合并part1和part2---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:11.475594+0800 MultithreadingDemo[28408:4292323] 开始写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
2019-12-29 11:50:12.478816+0800 MultithreadingDemo[28408:4292323] 完成写入磁盘---<NSThread: 0x6000030c4a80>{number = 13, name = (null)}
从上面运行结果可以看出,异步栅栏函数不会阻塞当前线程,也就是说UI界面并不会被卡死。
5.3 注意事项
- 全局队列对栅栏函数是不生效的,必须是自己创建的并发队列。
- 所有任务(包括栅栏函数添加的任务)都必须在同一个派发队列中,否则栅栏函数不生效。使用第三方网络框架(比如AFNetworking)进行网络请求时使用栅栏函数无效的正是因为这个原因导致。
6. 任务组(dispatch_group)
前面栅栏函数实现的需求也可以通过任务组来实现。GCD中关于任务组的API如下:
// 创建一个任务组(任务组本质上是一个值为LONG_MAX的信号量dispatch_semaphore_t)
dispatch_group_t dispatch_group_create(void);
// 向任务组中添加任务的异步函数。
// 参数一:任务组; 参数二:派发队列; 参数三:任务block
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// 监听group组中任务的完成状态,当所有的任务都执行完成后,触发block块
// 参数一:任务组;
// 参数二:是第三个参数block所处的派发队列,这个队列和任务组中的任务的队列可以不是同一个队列,比如任务组中的任务都完成后需要刷新UI,那这个队列就是主队列;
// 参数三:任务组中的任务都完成后要执行的block
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// dispatch_group_enter是将block任务添加到queue队列,并被group组管理,任务组的任务数+1
// dispatch_group_leave是相应的任务执行完成,任务组的任务数-1
// 这两个API是成对出现的
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
// 会阻塞当前线程,等其前面的任务都执行完了后才会执行其后面的代码
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
现需求如下:
某个界面需要请求banner信息和产品列表信息,等这两个接口的数据都返回后再回到主线程刷新UI。
这个需求通过栅栏函数和任务组都可以实现,任务组可以通过dispatch_async
、dispatch_group_enter
和dispatch_group_leave
这3个API配合使用来实现,也可以通过dispatch_group_async
这个API来实现。
6.1 dispatch_async
、dispatch_group_enter
和dispatch_group_leave
// dispatch_group_enter()、dispatch_group_leave()和dispatch_async()配合使用
- (void)GCDGroup1{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"当前线程1");
dispatch_group_enter(group); // 开始任务前将任务交给任务组管理,任务组中任务数+1
dispatch_async(queue, ^{
NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"收到banner数据---%@",[NSThread currentThread]);
dispatch_group_leave(group); // 任务结束后将任务从任务组中移除,任务组中任务数-1
});
NSLog(@"当前线程2");
dispatch_group_enter(group); // 任务组中任务数+1
dispatch_async(queue, ^{
NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
dispatch_group_leave(group); // 任务组中任务数-1
});
NSLog(@"当前线程3");
// 监听任务组中的任务的完成情况,当任务组中所有任务都完成时指定队列安排执行block中的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
});
NSLog(@"当前线程4");
}
// ****************打印结果****************
2019-12-30 08:56:19.991427+0800 MultithreadingDemo[35831:4040872] 当前线程1
2019-12-30 08:56:19.991561+0800 MultithreadingDemo[35831:4040872] 当前线程2
2019-12-30 08:56:19.991607+0800 MultithreadingDemo[35831:4040962] 开始请求banner数据---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:19.991664+0800 MultithreadingDemo[35831:4040872] 当前线程3
2019-12-30 08:56:19.991708+0800 MultithreadingDemo[35831:4044340] 开始请求产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:19.991738+0800 MultithreadingDemo[35831:4040872] 当前线程4
2019-12-30 08:56:20.992641+0800 MultithreadingDemo[35831:4040962] 收到banner数据---<NSThread: 0x600003f91f80>{number = 7, name = (null)}
2019-12-30 08:56:22.993730+0800 MultithreadingDemo[35831:4044340] 收到产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:56:22.993924+0800 MultithreadingDemo[35831:4040872] 回到主线程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
6.2 dispatch_group_async
- (void)GCDGroup2{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"当前线程1");
dispatch_group_async(group, queue, ^{
NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"收到banner数据---%@",[NSThread currentThread]);
});
NSLog(@"当前线程2");
dispatch_group_async(group, queue, ^{
NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
});
NSLog(@"当前线程3");
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
});
NSLog(@"当前线程4");
}
// ****************打印结果****************
2019-12-30 08:59:33.989079+0800 MultithreadingDemo[35831:4040872] 当前线程1
2019-12-30 08:59:33.989212+0800 MultithreadingDemo[35831:4040872] 当前线程2
2019-12-30 08:59:33.989248+0800 MultithreadingDemo[35831:4045455] 开始请求banner数据---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:33.989299+0800 MultithreadingDemo[35831:4040872] 当前线程3
2019-12-30 08:59:33.989313+0800 MultithreadingDemo[35831:4044340] 开始请求产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:33.989358+0800 MultithreadingDemo[35831:4040872] 当前线程4
2019-12-30 08:59:34.992763+0800 MultithreadingDemo[35831:4045455] 收到banner数据---<NSThread: 0x600003f9a3c0>{number = 9, name = (null)}
2019-12-30 08:59:36.992068+0800 MultithreadingDemo[35831:4044340] 收到产品列表数据---<NSThread: 0x600003f91cc0>{number = 8, name = (null)}
2019-12-30 08:59:36.992348+0800 MultithreadingDemo[35831:4040872] 回到主线程刷新UI---<NSThread: 0x600003fe6d00>{number = 1, name = main}
从上面的打印结果可以看出,这两种实现方式的效果是一样的,dispatch_group_notify
监听任务组并不会阻塞当前线程。
6.3 dispatch_group_wait与dispatch_group_notify的区别
我们把上面代码中的dispatch_group_notify
换成dispatch_group_wait
再看看运行结果。
// dispatch_group_wait的使用
- (void)GCDGroup3{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"当前线程1");
dispatch_group_async(group, queue, ^{
NSLog(@"开始请求banner数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f]; // 模拟下载耗时1s
NSLog(@"收到banner数据---%@",[NSThread currentThread]);
});
NSLog(@"当前线程2");
dispatch_group_async(group, queue, ^{
NSLog(@"开始请求产品列表数据---%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f]; // 模拟下载耗时1s
NSLog(@"收到产品列表数据---%@",[NSThread currentThread]);
});
NSLog(@"当前线程3");
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"回到主线程刷新UI---%@",[NSThread currentThread]);
// });
// 将等待时间设置为DISPATCH_TIME_FOREVER,表示永不超时,等任务组中任务全部都完成后才会执行其后面的代码
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"任务组中的任务全部完成,刷新UI");
NSLog(@"当前线程4");
}
// ****************打印结果****************
2019-12-30 09:10:12.841430+0800 MultithreadingDemo[36021:4052387] 当前线程1
2019-12-30 09:10:12.841566+0800 MultithreadingDemo[36021:4052387] 当前线程2
2019-12-30 09:10:12.841604+0800 MultithreadingDemo[36021:4052474] 开始请求banner数据---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:12.841660+0800 MultithreadingDemo[36021:4052387] 当前线程3
2019-12-30 09:10:12.841704+0800 MultithreadingDemo[36021:4052591] 开始请求产品列表数据---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:13.842615+0800 MultithreadingDemo[36021:4052474] 收到banner数据---<NSThread: 0x60000111dcc0>{number = 2, name = (null)}
2019-12-30 09:10:15.842548+0800 MultithreadingDemo[36021:4052591] 收到产品列表数据---<NSThread: 0x600001151680>{number = 6, name = (null)}
2019-12-30 09:10:15.842738+0800 MultithreadingDemo[36021:4052387] 任务组中的任务全部完成,刷新UI
2019-12-30 09:10:15.842826+0800 MultithreadingDemo[36021:4052387] 当前线程4
结果发现dispatch_group_wait
虽然实现了需求,但是有个问题,它阻塞了当前线程
,效果和同步栅栏函数
一样。
对于dispatch_group_wait
函数的第二个参数需特别说明一下,设置为DISPATCH_TIME_FOREVER
表示永不超时,等任务组中任务全部都完成后才会执行其后面的代码。但如果将其设置为一个具体的时间,比如下面设置为2秒,那么如果任务组中的所有任务需要1秒执行完,那么就只需要等待1秒就可以执行后面的代码;如果任务组中全部任务执行完要3秒,那么等待2秒后就会执行后面的代码。
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)));
7 dispatch_after和dispatch_time_t
7.1 dispatch_after
dispatch_after
是GCD中用于延迟将某个任务添加到队列中,其定义如下:
void dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
- 第一个参数
when
:数据类型是dispatch_time_t
(后面会详细介绍),表示延迟多长时间开始执行。 - 第二个参数
queue
:管理要延迟执行的任务的派发队列。 - 第三个参数
block
:要延迟执行的任务块。
比如我要从现在开始,延迟3秒后在主线程刷新UI,其代码如下。
// dispatch_after
// 需求:从现在开始,延迟3秒后在主线程刷新UI。
- (void)dispatchAfter{
NSLog(@"现在时间--%@",[NSDate date]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"到主线程刷新UI--%@",[NSDate date]);
});
}
// ****************打印结果****************
2019-12-30 10:19:00.397355+0800 MultithreadingDemo[36250:4082843] 现在时间--2019-12-30 02:19:00 +0000
2019-12-30 10:19:03.397714+0800 MultithreadingDemo[36250:4082843] 到主线程刷新UI--2019-12-30 02:19:03 +0000
7.2 dispatch_time_t
创建dispatch_time_t
类型数据的函数有2个:dispatch_time
和dispatch_walltime
。
7.2.1 dispatch_time
dispatch_time
定义如下:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
- 参数一
when
:也是一个dispatch_time_t
类型的数据,表示从什么时间开始,一般直接传DISPATCH_TIME_NOW
表示从现在开始。 - 参数二
delta
:表示具体的时间长度,delta
的单位是纳秒,所以不能直接传 int 或 float, 需要写成这种形式(int64_t)3 * NSEC_PER_SEC
来表示3秒。 前面dispatch_after
的示例代码中就是用dispatch_time
来创建的dispatch_time_t
,下面我们主要介绍一下第二个参数中用到的一些关于时间的宏:
#define NSEC_PER_SEC 1000000000ull 每秒有1000000000纳秒
#define NSEC_PER_MSEC 1000000ull 每毫秒有1000000纳秒
#define USEC_PER_SEC 1000000ull 每秒有1000000微秒
#define NSEC_PER_USEC 1000ull 每微秒有1000纳秒
再次强调一下delta
单位是纳秒
,所以我们表示1秒可以有如下几种写法:
1 * NSEC_PER_SEC
1000 * NSEC_PER_MSEC
(表示1000毫秒)USEC_PER_SEC * NSEC_PER_USEC
7.2.2 dispatch_walltime
dispatch_walltime
定义如下:
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);
- 参数一
when
:表示从什么时间开始,是一个结构体,可以创建一个绝对的时间点(比如2019-12-30 10:50:55)。也可以传NULL
表示从当前时间开始。 - 参数二
delta
:和dispatch_time函数的第二个参数一样。
// dispatch_walltime
// 需求:从一个具体时间点开始,再晚10秒执行任务
- (void)dispatchWallTime{
NSString *dateStr = @"2019-12-30 11:09:00";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT+0800"];
NSDate *date = [formatter dateFromString:dateStr];
NSTimeInterval timeInterval = [date timeIntervalSince1970];
// dispatch_walltime第一个参数的结构体
struct timespec timeStruct;
timeStruct.tv_sec = (NSInteger)timeInterval;
NSLog(@"设置的时间点--%@",[formatter stringFromDate:date]);
// 比时间点再晚10秒
dispatch_time_t time = dispatch_walltime(&timeStruct, (int64_t)(10 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"到主线程刷新UI--%@",[formatter stringFromDate:[NSDate date]]);
});
}
// ****************打印结果****************
2019-12-30 11:08:40.525710+0800 MultithreadingDemo[36430:4106292] 设置的时间点--2019-12-30 11:09:00
2019-12-30 11:09:10.000208+0800 MultithreadingDemo[36430:4106292] 到主线程刷新UI--2019-12-30 11:09:10
两者之间的区别:
dispatch_time
创建的是一个相对时间,它参考的是当前系统的时钟,当设备进入休眠后,系统时钟也会进入休眠状态,此时dispatch_time
也会被挂起。比如10:00分开始执行dispatch_time
,并且60分钟后执行某个任务。10:10分设备休眠了,10:40分设备从休眠中唤醒(共休眠了30分钟),那么从唤醒时刻开始,再等待50分钟(也就是11:30分)才会执行任务。dispatch_walltime
创建的是一个绝对的时间点,比如上面的例子,一旦创建就表示从10:00开始,60分钟之后(也就是11:00)执行任务,它不会受到休眠的影响。
8. 快速迭代方法:dispatch_apply()
dispatch_apply
类似for循环,会在指定的队列中多次执行任务。其定义如下:
void dispatch_apply(size_t iterations,
dispatch_queue_t queue,
DISPATCH_NOESCAPE void (^block)(size_t));
- 参数一
iterations
:执行的次数 - 参数二
queue
:提交任务的队列 - 参数三
block
:执行任务的代码块
- (void)dispatchApply{
NSLog(@"开始");
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"第%ld次开始执行--%@",index,[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.0f];
NSLog(@"第%ld次结束执行--%@",index,[NSThread currentThread]);
});
NSLog(@"结束");
}
// ****************打印结果****************
2019-12-30 11:59:35.025320+0800 MultithreadingDemo[36604:4129509] 开始
2019-12-30 11:59:35.025457+0800 MultithreadingDemo[36604:4129509] 第0次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:35.025616+0800 MultithreadingDemo[36604:4129600] 第1次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:35.025719+0800 MultithreadingDemo[36604:4129599] 第2次开始执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:35.025735+0800 MultithreadingDemo[36604:4129598] 第3次开始执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.025791+0800 MultithreadingDemo[36604:4129509] 第0次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.025944+0800 MultithreadingDemo[36604:4129599] 第2次结束执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.025945+0800 MultithreadingDemo[36604:4129600] 第1次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.025951+0800 MultithreadingDemo[36604:4129598] 第3次结束执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:38.026063+0800 MultithreadingDemo[36604:4129509] 第4次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:38.026082+0800 MultithreadingDemo[36604:4129600] 第6次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:38.026086+0800 MultithreadingDemo[36604:4129599] 第5次开始执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:38.026145+0800 MultithreadingDemo[36604:4129598] 第7次开始执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129600] 第6次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027346+0800 MultithreadingDemo[36604:4129509] 第4次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:41.027384+0800 MultithreadingDemo[36604:4129598] 第7次结束执行--<NSThread: 0x6000030ed240>{number = 6, name = (null)}
2019-12-30 11:59:41.027402+0800 MultithreadingDemo[36604:4129599] 第5次结束执行--<NSThread: 0x6000030e92c0>{number = 5, name = (null)}
2019-12-30 11:59:41.027504+0800 MultithreadingDemo[36604:4129600] 第8次开始执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:41.027506+0800 MultithreadingDemo[36604:4129509] 第9次开始执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129600] 第8次结束执行--<NSThread: 0x60000309cb00>{number = 4, name = (null)}
2019-12-30 11:59:44.027923+0800 MultithreadingDemo[36604:4129509] 第9次结束执行--<NSThread: 0x600003095680>{number = 1, name = main}
2019-12-30 11:59:44.028156+0800 MultithreadingDemo[36604:4129509] 结束
由打印结果可以看出对于并发队列dispatch_apply
会创建多个线程去并发执行,而且会阻塞当前线程,等所有任务都完成后才会继续执行后面的代码。
对于串行队列不会开启新的线程,而是会在当前线程中串行执行。
9. dispatch_once
GCD提供了dispatch_once()
函数保证在应用程序生命周期中只执行一次指定处理。比如来生成单例。
- (void)dispatchOnce{
static ViewController *vc = nil;
static dispatch_once_t onceToken;
dispatch_apply(3, dispatch_get_global_queue(0, 0), ^(size_t idx) {
NSLog(@"第%ld次开始执行--%@",idx,[NSThread currentThread]);
dispatch_once(&onceToken, ^{
vc = [[ViewController alloc] init];
NSLog(@"是否只执行了一次--%@",[NSThread currentThread]);
});
NSLog(@"第%ld次结束执行--%@",idx,[NSThread currentThread]);
});
}
// ****************打印结果****************
2019-12-30 12:14:43.911414+0800 MultithreadingDemo[36690:4137229] 第0次开始执行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911421+0800 MultithreadingDemo[36690:4137379] 第1次开始执行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
2019-12-30 12:14:43.911461+0800 MultithreadingDemo[36690:4137378] 第2次开始执行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911613+0800 MultithreadingDemo[36690:4137229] 是否只执行了一次--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911748+0800 MultithreadingDemo[36690:4137229] 第0次结束执行--<NSThread: 0x600002b7e0c0>{number = 1, name = main}
2019-12-30 12:14:43.911750+0800 MultithreadingDemo[36690:4137378] 第2次结束执行--<NSThread: 0x600002b0c280>{number = 7, name = (null)}
2019-12-30 12:14:43.911765+0800 MultithreadingDemo[36690:4137379] 第1次结束执行--<NSThread: 0x600002b1df40>{number = 6, name = (null)}
10. dispatch_semaphore(信号量)
dispatch_semaphore
用于控制最大并发数。其主要涉及到3个函数:
// 创建信号量API
dispatch_semaphore_t dispatch_semaphore_create(long value);
dispatch_semaphore_create()
创建并返回一个dispatch_semaphore_t类型的信号量,传入的参数必须大于等于0,否则会返回NULL。传入的参数value就是信号量的初始值,也可以理解为最大并发数。当这个值设置为1时,最大并发数为1,可以当成锁来使用。
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_wait()
函数的第一个参数是信号量,第二个参数是等待超时时间。这个函数首先会判断信号量的值是否大于0,如果大于0,那么信号值减1并继续执行后续代码;如果信号值等于0,那么就阻塞当前线程进行等待,直到信号值大于0或等待超时时才会继续执行后续代码。
// 发送信号
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
dispatch_semaphore_signal()
函数用来发送信号,发送信号后信号量的值会+1,它可以使处于等待状态的线程被唤醒。
// 信号量
- (void)dispatchSemaphore{
dispatch_queue_t queue = dispatch_queue_create("com.demo.tsk", DISPATCH_QUEUE_CONCURRENT);
// 创建信号量并设置信号值(最大并发数)为2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
for (NSInteger i = 0; i < 5; i++) {
// 如果信号值大于0,信号值减1并执行后续代码
// 如果信号值等于0,当前线程将被阻塞处于等待状态,直到信号值大于0或者等待超时为止
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"第%ld次开始执行--%@",i,[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"第%ld次结束执行--%@",i,[NSThread currentThread]);
// 任务执行完后发送信号使信号值+1
dispatch_semaphore_signal(semaphore);
});
}
NSLog(@"******当前线程******");
}
// ****************打印结果****************
2019-12-30 14:33:16.757992+0800 MultithreadingDemo[36982:4182131] 第0次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762332+0800 MultithreadingDemo[36982:4182131] 第0次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:17.762541+0800 MultithreadingDemo[36982:4182131] 第1次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764500+0800 MultithreadingDemo[36982:4182131] 第1次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:18.764710+0800 MultithreadingDemo[36982:4182131] 第2次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766611+0800 MultithreadingDemo[36982:4182131] 第2次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:19.766765+0800 MultithreadingDemo[36982:4182131] 第3次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770737+0800 MultithreadingDemo[36982:4182131] 第3次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:20.770932+0800 MultithreadingDemo[36982:4182040] ******当前线程******
2019-12-30 14:33:20.770944+0800 MultithreadingDemo[36982:4182131] 第4次开始执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
2019-12-30 14:33:21.771514+0800 MultithreadingDemo[36982:4182131] 第4次结束执行--<NSThread: 0x600001a11500>{number = 3, name = (null)}
11. dispatch_source(实现定时器)
dispatch_source
是GCD中的一个基本类型,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。关于dispatch_source
的详细介绍可以参考文章(iOS dispatch_source_t的理解)。
下面我们通过dispatch_source
来实现一个倒计时的功能。
- (void)dispatchSource{
__block NSInteger timeout = 10; // 倒计时时间
dispatch_queue_t queue = dispatch_queue_create("my.queue", DISPATCH_QUEUE_CONCURRENT);
/*
创建一个dispatch_source_t对象(其本质是一个OC对象)
第一个参数是要监听的事件的类型
第4个参数是回调函数所在的队列
第2和第3个参数是和监听事件类型(第一个参数)有关的,监听事件类型是DISPATCH_SOURCE_TYPE_TIMER时这两个参数都设置为0就可以了。
具体的可以参考博客 https://www.cnblogs.com/wjw-blog/p/5903441.html
*/
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
/*
设置计时器的一些参数
第一个参数是前面创建的dispatch_source_t对象
第二个参数是计时器开始的时间
第三个参数是计时器间隔时间
第四个参数是是一个微小的时间量,单位是纳秒,系统为了改进性能,会根据这个时间来推迟timer的执行以与其它系统活动同步。也就是设置timer允许的误差。
*/
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
// 设置回调事件
dispatch_source_set_event_handler(self.timer, ^{
timeout--;
NSLog(@"%ld",timeout);
if (timeout <= 0) {
// 结束倒计时
dispatch_source_cancel(self.timer);
}
});
dispatch_resume(self.timer);
}
上面只是简单通过GCD实现了计时器功能,我们完全可以基于GCD自己封装一个和NSTimer
功能类似的计时器。因为NSTimer
是基于Runloop
的,使用过程中经常会遇到一些坑,而且NSTimer
计时器没有GCD计时器精准。
自己封装GCD计时器时一定要注意,dispatch_source_t
类型的timer
不要定义为局部变量,否则定时器不起作用。