三、队列调度机制的更多技巧
通过上面的演示,我们已经可以运用队列进行多线程的执行任务,但是GCD的强大之处远远不止如此。
1、使用队列组
如果有这样三个任务,A与B是没有关系的,他们可以并行执行,C必须在A,B结束之后才能执行,当然,实现这样的逻辑并不困难,使用KVO就可以实现,但是使用队列组处理这样的逻辑,代码会更加清晰简单。
可以使用dispatch_group_create()创建一个队列组,使用如下函数将队列添加到队列组中:
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
队列组中的队列是异步执行的,示例如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//创建一个队列组
dispatch_group_t group=dispatch_group_create();
创建一个异步队列
dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i=0; i<10; i++) {
NSLog(@"%@:%d",[NSThread currentThread],i);
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<10; i++) {
NSLog(@"%@:%d",[NSThread currentThread],i);
}
});
//阻塞线程直到队列任务完成
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
for (int i=0; i<10; i++) {
NSLog(@"over:%d",i);
}
}
打印出来的信息如下:
可以看出,队列中的任务是异步执行的,并且等待队列组中队列任务全部执行后才执行后面的任务。这样的做法在实际应用中我们很少使用,通常我们会把后续的任务在放在异步中执行,做法如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//创建一个队列组
dispatch_group_t group=dispatch_group_create();
//创建一个队列
dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
//添加队列任务到队列组
dispatch_group_async(group, queue, ^{
for (int i=0; i<10; i++) {
NSLog(@"%@:%d",[NSThread currentThread],i);
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<10; i++) {
NSLog(@"%@:%d",[NSThread currentThread],i);
}
});
//队列组任务执行完后执行的任务
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=0; i<10; i++) {
NSLog(@"over:%d",i);
}
});
for (int i=0; i<10; i++) {
NSLog(@"Finish:%d",i);
}
}
打印信息如下:
可以看出GCD的强大了吧,复杂的任务逻辑关系因为GCD变得十分清晰简单。
2、循环机制
一开始我们就提到,GCD相比NSOperation的优势在于多核心的应用,更深得挖掘出了硬件的性能。GCD在多核方面的一个明显的特点就是循环机制。
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
NSLog(@"%@:%zu",[NSThread currentThread],i);
});
打印结果如下:
可以看出,程序的运行效率又会高许多。
3、消息传递机制
dispatch_source_t类型的对象可以用来传递和接受某个消息,然后执行block方法,示例如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//创建一个数据对象,DISPATCH_SOURCE_TYPE_DATA_ADD的含义表示数据变化时相加
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
//创建接收数据变化的句柄
dispatch_source_set_event_handler(source, ^{
NSLog(@"%lu",dispatch_source_get_data(source));
});
//启动
dispatch_resume(source);
//设置数据
dispatch_source_merge_data(source, 1);
//这步执行完之后会执行打印方法
}
4、发送和等待信号
GCD中还有一个重要的概念是信号量。它的用法法消息的传递有所类似,通过代码来解释:
//创建一个信号,其中的参数为信号的初始值
dispatch_semaphore_t singer = dispatch_semaphore_create(0);
//发送信号,使信号量+1
dispatch_semaphore_signal(singer);
//等待信号,当信号量大于0时执行后面的方法,否则等待,第二个参数为等待的超时时长,下面设置的为一直等待
dispatch_semaphore_wait(singer, DISPATCH_TIME_FOREVER);
NSLog(@"123");
通过发送信号,可以试信号量+1,每次执行过等待信号后,信号量会-1;如此,我们可以很方便的控制不同队列中方法的执行流程。
5、挂起和开启任务队列
GCD还提供了暂停与开始任务的方法,使用
void dispatch_suspend(dispatch_object_t object);
可以将队列或者队列组进行暂时的挂起,使用
void dispatch_resume(dispatch_object_t object);
将队列或者队列组重新开启。
需要注意的是,暂停队列时,队列中正在执行的任务并不会被中断,会挂起未开启的任务。
四、关于内存管理
GCD虽然是基于C语言封装的框架,使用了面向对象的思想。因此,它的内存管理是需要我们注意的,不论是ARC或者MRC,我们都应该手动去处理这些对象。还好,GCD的内存管理思路和Object—C是兼容的,我们使用dispatch_retain()和dispatch_release()来将引用对象的计数进行加减。这一点十分重要,切记切记。