原创,转载请注明出处。
抛砖引玉。最近在复习了《Obj-C高级编程》这本书后,一方面记录一下知识点,另一方便加了一些自己的理解。结合一些经典的例子以及实际使用场景加深理解,权当学习交流之用。
需要了解的基本概念
1.同步执行:阻塞当前线程。
2.异步执行:不阻塞当前线程。
3.串行队列:按照FIFO原则出列,一个一个的执行。
4.并行队列: 一起执行。
后续内容会再做解释。
基础API
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
这是gcd 中最常见的两个API,其中:
参数部分
- 第一个参数
dispatch_queue_t
代表放在哪个队列执行,系统提供了几种队列:
-
dispatch_get_global_queue(long identifier, unsigned long flags)
全局并行队列。第一个参数表示优先级,最后一个参数写0就可以了。系统提供了四种优先级:(优先级由高到低)
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
一般dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
这么写就行了。
-
dispatch_get_main_queue()
主队列,也就是主线程的执行队列。此为串行队列。按照FIFO原则执行。 - 同样我们也可以自定义队列:
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
第一个参数为队列的名称,《Obj-C高级编程》作者推荐使用应用ID这种逆序全程域名的命名方式:"com.example.gcd.MyConcurrentDispatchQueue",该名称会出现在程序崩溃的crashlog中。便于排查问题。
第二个参数代表队列类型,NULL
或DISPATCH_QUEUE_SERIAL
创建串行队列。DISPATCH_QUEUE_CONCURRENT
创建并行队列。
- 第二个参数
dispatch_block_t
是一个block,我们把需要使用GCD执行的任务放在这个block里。
函数部分
dispatch_sync
:代表同步执行,对应基础概念里的同步执行。
这个方法会阻塞当前线程,将第二个参数block里的任务追加到第一个参数指定的队列queue里执行。直到block里的任务执行完毕,程序才继续往下运行。
假如当前线程的执行队列和第一个参数里的queue是同一个队列,且都是串行队列,那么就会造成死锁。(见代码1.1.1,1.1.2)
dispatch_async
:代表异步执行。不阻塞当前线程,即使用多个线程同时执行多个处理。其中异步执行一个并行队列,XNU内核会基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定要不要开辟新的线程,以及开辟多少个线程来处理。(代码1.2)
代码 1.1.1 死锁
当前线程为主线程,当前队列为主队列。queue内参数也为主队列,同时他们都是串行队列。那么按照刚刚我们总结的简单理论,发生死锁。
NSLog(@"程序开始运行");
//主线程阻塞,开始执行block里的任务
dispatch_sync(dispatch_get_main_queue(), ^{
//task2
NSLog(@"此句不执行,加到主队列中执行,排在task3后面,按照FIFO原则需要等待 task3执行完毕才能执行");
});
// task2 等待task3, task3 等待 task2 .死锁
NSLog(@"此句不执行,主线程主队列死锁");
我们将dispatch_get_main_queue
替换为dispatch_get_global_queue
使得二者不为同一个串行队列,则不会发生死锁。同学们可以自行试验。
代码 1.1.2 死锁 。
当前执行队列和dispatch_sync
queue参数都为同一个同步队列。发生死锁。
NSLog(@"task1");
dispatch_queue_t otherQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(otherQueue, ^{
NSLog(@"task2");
//发生死锁,当前执行队列和dispatch_sync 执行队列相同且都是同步队列
dispatch_sync(otherQueue, ^{
NSLog(@"task4");
//task4 排在otherQueue执行任务task5之后,需要task5执行完毕才可以执行。
});
//同步执行,需要task4 执行完毕才可以执行task5。
//task4 等待task5, task5 等待task4,发生死锁。
NSLog(@"task5");
});
//打印 task1 task2 task3(task2,task3顺序不定)
NSLog(@"task3");
同样我们可以将dispatch_sync
里的otherQueue替换为任意一个非相同队列,则不会发生死锁。这里也不再赘述。
代码 1.2 是否开启新线程,以及开辟多少个。
dispatch_queue_t queue1 = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.test.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"thread%@",[NSThread currentThread]);
dispatch_async(queue2, ^{
NSLog(@"thread%@",[NSThread currentThread]);
});
});
//有时打印
//thread{number = 4, name = (null)}
//thread{number = 4, name = (null)}
//有时打印
//thread{number = 4, name = (null)}
//thread{number = 6, name = (null)}
以上试验也可以验证了这一理论,有时只需要开辟一个线程即可处理。有时需要开辟两个新线程。
iOS和OS X的核心--XNU内核决定应当使用的线程数,并只生成所需的线程执行处理。另外,当处理结束,应当执行的处理数减少时,XNU内核会结束不再需要的线程。XNU内核仅使用Concurrent Dispatch Queue便可完美地管理并行执行多个处理的线程。
dispatch_set_target_queue
dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue);
此API,可以改变执行队列优先级以及队列类型。
- Concurrent Dispatch Queue 改 Serial Dispatch Queue :
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(concurrentQueue, serialQueue);
dispatch_async(concurrentQueue, ^{
NSLog(@"task1 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task2 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task3 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task4 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task5 thread:%@",[NSThread currentThread]);
});
注释掉
dispatch_set_target_queue
这行,会无序打印task1-5。
加上后实际上执行队列由并行变成了串行执行,task1-5按顺序打印。
- 改变队列优先级
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(concurrentQueue, globalQueue);
dispatch_queue_create
函数生成的Dispatch Queue 不管是Serial Queue 还是 Concurrent Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue 的执行优先级要使用dispatch_set_target_queue
函数。
dispatch_after
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
参数:
1.时间(指定时间追加处理到Dispatch Queue)。
2.队列。
3.任务。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"3秒后追加到主线程队列里执行");
});
因为主线程主队列,在主线程RunLoop中执行,所以在每隔1/60秒执行的RunLoop中,任务最快在3秒后执行,最慢在3+1/60秒后执行。如果主队列有大量处理,那么这个时间会更长。
Dispatch Group
在多个并行执行的任务全部执行完毕后,想要追加一个结束处理。这种场景往往比较常见。虽然可以通过别的方式实现,但逻辑会变的复杂,代码也不雅观。这时候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(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"task Done");
});
//打印 (task1-3 无序)
//task1
//task2
//task3
//task Done
一个简单的demo,在任务1-3完成后,执行task Done
除了使用dispatch_group_notify
API 处理group任务结束外,还可以使用dispatch_group_wait
函数。同样的例子:
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(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
});
//也可以使用dispatch_group_wait 函数
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"task Done");
DISPATCH_TIME_FOREVER
代表永久等待。
我们也可以指定等待的时间,下例等待1秒,超过1秒不再等待。
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(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
for (int i = 0; i < 10000000; i++){
@autoreleasepool{
NSString* string = @"ab c";
//生成autorelease对象
NSArray* array = [string componentsSeparatedByString:string];
}
}
});
//DISPATCH_TIME_FOREVER 永久等待,同样我们可以设置等待的时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
// 属于Dispatch Group 的全部处理执行结束
NSLog(@"task Done");
} else {
// 属于Dispatch Group 的某个处理还在执行中
NSLog(@"task Doing");
}
通过
dispatch_group_wait
返回值可以判断,是group任务在设置的超时时间内完成,还是超时未完成。 result ==0 代表全部处理完成,非0代表执行超时了。
但假如Dispatch Queue 里的任务是一个个网络请求的话,由于网络请求是异步执行,那么实际达不到我们想要的在所有请求完毕后执行某段代码的目的。那么这时就可以借助信号量Dispatch Semaphore
来完成了。
Dispatch Semaphore
- dispatch_semaphore_create(long value) 创建一个信号量。
- dispatch_semaphore_signal(dispatch_semaphore_t dsema) 信号量加1
- dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 等待信号大0时执行,并对信号量进行减一操作。
1.上述在Dispatch Queue里并行执行多个网络请求的情况,想要在所有请求都完成的情况执行某段代码就可以使用Dispatch Semaphore了。
- (void)requestDemo{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weak typeof(self) weak_self = self;
dispatch_group_async(group, queue, ^{
NSLog(@"请求任务A");
[weak_self requestA];
});
dispatch_group_async(group, queue, ^{
NSLog(@"请求任务B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"请求任务C");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"所有请求完成");
});
}
- (void)requestA{
// 用于GCD Group 以及 NSOperationQueue中设置依赖关系的任务,因为网络请求异步执行,
// 不会阻塞当前线程,达不到按序执行的效果。
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// [异步请求:{
// 成功: dispatch_semaphore_signal(sema);
// 失败: dispatch_semaphore_signal(sema);
// }];
//一直等待到信号量大于0才执行,并减1
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
异步请求方法前创建为0的信号量,请求结束后信号量+1,
dispatch_semaphore_wait
会等到信号量大于才继续运行。整个请求模块会在dispatch_semaphore_wait
可以继续运行才标记为block任务结束。
2.控制异步执行Dispatch Concurrent Queue最大并发数。
众所周知NSOperationQueue
便于管理多线程,可以设置maxConcurrentOperationCount
来控制多线程执行的最大并发数。那么GCD要如何控制最大并发呢?这时Dispatch Semaphore又发挥作用了。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//初始信号量1 ,这里1可以为n
dispatch_semaphore_t sema = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 10; i++) {
//大于0执行,并减1
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"%ld",i);
//任务完成,信号量加1
dispatch_semaphore_signal(sema);
});
}
//按顺序打印0-9
通过设置
dispatch_semaphore_create (1)
设置最大并发1,那么实际上就把并发队列设置成了一个串行队列。dispatch_semaphore_create (n)
则最大并发为n,如果n设置的很大,实际上达不到n。因为苹果内核决定了此次GCD执行的并发队列所需要的线程数。
未完待续。。。
后续补充:
dispatch_barrier_async
dispatch_apply
dispatch_once