关键词:异步执行任务的技术、将应用程序的线程管理用的代码在系统级中实现、高效率。
旧的 API 实现
- (void)demoPerformSelector{
[self performSelectorInBackground:@selector(doWork) withObject:nil];
}
- (void)doWork{
NSLog(@"doWork........");
[self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
}
- (void)doneWork{
NSLog(@"doneWork!");
}
使用 GCD 实现
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"doWork........");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"doneWork!");
});
});
1.Dispatch Queue 调度队列
Dispatch Queue 先进先出的属性(FIFO) 执行处理。有两种 Dispatch Queue,一种是等待现在执行中处理的 Serial Dispatch Queue(串行调度队列),另一种是不等待现在执行中处理的 Concurrent Dispatch Queue(并行调度队列)。
通过以下源码,比较这两种 Dispatch Queue:
dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);
dispatch_async(queue,block5);
当 queue 为 Serial Dispatch Queue 时,因为要等到现在执行中处理结束,所以首先执行 block0,block0执行结束后,执行 block1,如此重复,同时执行的处理数只能有1个。即执行该源代码后,一定按照以下顺序进行处理。(block0,block1,block2,block3,block4,block5)。
当 queue 为 Concurrent Dispatch Queue 时,因为不用等待现在执行中的处理结束,所以首先执行 block0,不管 block0 的执行是否结束,都开始执行后面的 block1,不管 block1执行是否结束了,都开始执行后面的 block2,如此重复循环。
这样虽然不用等待处理结束,可以并行执行多个处理,但并行执行处理的数量取决于当前系统的状态。即 iOS 或 OS X 基于 Dispatch Queue 中的处理数、CPU核数以及 CPU 负荷等当前系统的状态来决定 Concurrent Dispatch Queue 中并行执行的处理数。所谓的“并行执行”,就是使用多个线程同时执行多个处理。
2.获取 Dispatch Queue
使用 dispatch_queue_create
创建 Dispatch Queue
//创建 Serial Dispatch Queue
dispatch_queue_t mySerialDicpatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
//创建 Concurrent Dispatch Queue
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.exmaple.gcd.MuConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
需要注意的是 dispatch_queue_create
函数可以创建任意多个 Dispatch Queue,当生成多个 Serial Dispatch Queue 时,虽然一个 Seria Dispatch Queue 中同时只能执行一个追加处理,但如果将处理分别追加到多个 Serial Dispatch Queue 中,各个 Serial Dispatch Queue 会分别执行,即同时执行多个处理。一旦生成 Serial Dispatch Queue 并追加处理,系统对于一个 Serial Dispatch Queue 就只生成并使用一个线程。如果生成 N 个 Serial Dispatch Queue,那么就生成 N 个线程。
Main Dispatch Queue 和 Global Dispatch Queue
Main Dispatch Queue,主线程队列,是一个 Serial Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
Global Dispatch Queue,全局队列,是 Concurrent Dispatch Queue,有四种优先级High Priority
、Default Priority
、Low Priority
、Background Priority
。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. dispatch_set_target_queue()
设置参考队列
dispatch_set_target_queue(dispatch_object_t object,
dispatch_queue_t _Nullable queue);
该函数有两种用法,第一种是设置 Dispatch Queue 的优先级
第一个参数填需要更改优先级的 Dispatch Queue,第二个参数填要与要指定的优先级相同优先级的 Global Dispatch Queue。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.gcd.mySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalQueueLowPriority);
第二种用法可以用来线程同步
当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的 target 指向新创建的队列
dispatch_queue_t serailQueue1 = dispatch_queue_create("com.gcd.serialQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serailQueue2 = dispatch_queue_create("com.gcd.serialQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("com.gcd.concurrentQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("com.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
//创建目标队列(参考队列)
dispatch_queue_t targetQueue = dispatch_queue_create("com.gcd.targetQueue", DISPATCH_QUEUE_SERIAL);
//设置参考
dispatch_set_target_queue(serailQueue1, targetQueue);
dispatch_set_target_queue(serailQueue2, targetQueue);
dispatch_set_target_queue(concurrentQueue1, targetQueue);
dispatch_set_target_queue(concurrentQueue2, targetQueue);
NSLog(@"******start******");
dispatch_async(serailQueue1, ^{
NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
sleep(3);
NSLog(@"task1 end");
});
dispatch_async(serailQueue2, ^{
NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
sleep(2);
NSLog(@"task2 end");
});
dispatch_async(concurrentQueue1, ^{
NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
sleep(1);
NSLog(@"task3 end");
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
NSLog(@"task4 end");
});
NSLog(@"******end******");
输出结果:
******start******
******end******
current Thread:{number = 5, name = (null)} task1
task1 end
current Thread:{number = 5, name = (null)} task2
task2 end
current Thread:{number = 5, name = (null)} task3
task3 end
current Thread:{number = 5, name = (null)} task4
task4 end
通过dispatch_set_target_queue()
函数以及参考队列targetQueue
,使得串行队列serailQueue1
,serailQueue2
与并行队列concurrentQueue1
,concurrentQueue2
同步。
4. dispatch_after()
延时执行(准确的说是追加任务)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"**********************");
});
注意:
dispatch_after()
函数并不是在指定时间后执行任务,而是在指定时间追加任务到 Dispatch Queue 中。
示例中与3秒后用 dispatch_async()
函数追加 block 中的任务到 Main Dispatch Queue 相同。
Main Dispatch Queue 在主线程的 RunLoop 中执行,假设每隔 1/60秒执行一次的 RunLoop,block 最快在3s 后执行,最慢在 3+1/60 秒后执行,并且在 Main Dispatch Queue 中有大量追加的任务或者主线程本身处理有延迟时,时间会更长。
dispatch_time_t
表示的是一个时刻,可以由 dispatch_time()
函数或者 dispatch_walltime()
函数获得
dispatch_time()
函数 能够获取从第一个参数指定的时间开始,到第二个参数指定的纳秒(毫微秒)后的时间 常用于计算相对时间
dispatch_walltime()
函数由 POSIX 中使用的 struct timespec 类型的时间得到 dispatch_time_t
类型的值,常用计算绝对时间
//ull 数值字面量(unsigned long long) DISPATCH_TIME_NOW 表示现在的时间
//获取从现在开始1s 后的时间
dispatch_time_t time1 = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
//获取从现在开始100毫秒后的时间
dispatch_time_t time2 = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
// 通过 NSDate 对象获取 dispatch_time_t 类型值
dispatch_time_t getDispatchTimeByDate(NSDate *date){
NSTimeInterval interval = [date timeIntervalSince1970];
double second;
double subsecond = modf(interval, &second);
struct timespec time;
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone = dispatch_walltime(&time, 0);
return milestone;
}
5. Dispatch Group
Dispatch Group可以用于在追加到 Dispatch Queue 中的多个任务全部结束后想执行的结束任务的操作。
-
dispatch_group_create()
创建 Dispatch Group -
dispatch_group_async()
追加任务到指定的 Dispatch Queue 中,且指定任务属于指定的 Dispatch Group -
dispatch_group_notify()
所有任务执行完毕后再追加执行的任务 -
dispatch_group_wati()
在指定的等待时间前(超时时间),等待 group 中全部任务处理结束,会卡住当前线程
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.gcd.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSLog(@"******start******");
dispatch_group_async(group, serialQueue, ^{
NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
sleep(3);
NSLog(@"task1 end");
});
dispatch_group_async(group, conCurrentQueue, ^{
NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
sleep(2);
NSLog(@"task2 end");
});
dispatch_group_async(group, serialQueue, ^{
NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
sleep(1);
NSLog(@"task3 end");
});
dispatch_group_async(group, conCurrentQueue, ^{
NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
NSLog(@"task4 end");
});
//第二个参数填超时时间 DISPATCH_TIME_FOREVER 表示永远等待
// long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 设置2秒的超时时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"Group 中所有任务执行完毕");
}else{
NSLog(@"Group 中任有任务执行中");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"current Thread:%@ All END",[NSThread currentThread]);
});
NSLog(@"******end******");
6. dispatch_barrier_async
dispatch_barrier_async
函数可以理解为一种分割任务的栅栏,通过dispatch_barrier_async
追加的任务同时只执行一个
dispatch_async(conCurrentQueue, read_block1);
dispatch_async(conCurrentQueue, read_block2);
dispatch_async(conCurrentQueue, read_block3);
dispatch_barrier_async(conCurrentQueue, write_block4);
dispatch_async(conCurrentQueue, read_block5);
dispatch_async(conCurrentQueue, read_block6);
dispatch_async(conCurrentQueue, read_block7);
示例中 block1,block2,block3 并行执行,都执行完毕后会执行 write_block4,然后block5,block6,block7再并行执行。
使用 Concurrent Dispatch Queue 配合 dispatch_barrier_async
函数可以实现高效的数据库访问和文件访问。
7. dispatch_sync
dispatch_sync
同步追加任务到队列中,不能开辟线程,且只有在追加的任务完成后才返回
dispatch_sync
函数引起死锁问题
产生死锁的条件是在串行队列所在的线程中,使用 dispatch_sync
函数追加任务到该串行队列中。
示例一
//在主线程中调用以下代码会产生死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"死锁~~");
NSLog(@"current thread:%@",[NSThread currentThread]);
});
示例二
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.gcd.serialDispatchQueue", NULL);
dispatch_async(serialDispatchQueue, ^{
NSLog(@"current thread:%@",[NSThread currentThread]);
dispatch_sync(serialDispatchQueue, ^{
NSLog(@"死锁");
});
});
示例一:由于主线程所在的队列 Main Dispatch Queue 为一个串行队列,所以在主线程中使用dispatch_sync
函数同步追加任务到 Main Dispatch Queue 中会产生死锁。
示例二:创建了串行队列 serialDispatchQueue
,使用dispatch_async
异步追加任务到该队列,此时该队列中的任务都是在该队列的线程上执行,此时使用dispatch_sync
函数再同步追加任务到该队列中,由于是在串行队列所在的线程中同步追加任务,所以产生了死锁。
dispatch_sync
函数引起死锁的原因
- 调用
dispatch_sync
函数会立即阻塞调用时该函数所在的线程,等待dispatch_sync
函数返回 - 由于追加任务的队列为串行队列所以,采用 FIFO 的顺序执行任务,很显然我们追加的任务位于队列后面,现在不会立即执行
- 如果任务不执行完,
dispatch_sync
函数不会返回,所以线程会一直被阻塞
8. dispatch_apply
dispatch_apply
函数会按指定的次数将任务 block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.gcd.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, concurrentDispatchQueue, ^(size_t index) {
NSLog(@"%zu current thread:%@",index,[NSThread currentThread]);
});
NSLog(@"******end******");
执行结果:0,2,1,3,4,5 end
因为是在 Concurrent Dispatch Queue 中执行任务的,所以几个任务是并行执行。
注意: dispatch_apply
函数会阻塞当前线程,等待任务全部执行完毕再返回,所以在主线程中调用追加任务到 Main Dispatch Queue 会造成死锁。
9. dispatch_suspend
和dispatch_resume
函数
dispatch_suspend
函数用于挂起指定的 Dispatch Queue
dispatch_resume
函数用于恢复指定的 Dispatch Queue
这些函数对已经开始执行的任务没有影响,挂起后,追加到 Dispatch Queue 中,但尚未执行的任务在此之后会暂停执行(任务仍然可以继续追加,但新追加的也会暂停执行),而恢复则使得这些任务继续执行。
10. Dispatch Semaphore 信号量
Dispatch Semaphore 信号量在 GCD 常被用于进行同步和控制并发。
信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被加1。当在一个线程上设置一个信号量等待时,线程会被阻塞至超时时间(如果有必要的话可以设置为一直阻塞),只有当计数器大于零,计数才会被减1并且该线程恢复。
信号量可以被用来作为线程锁,也可以用来控制并发线程数。
//如果设置为10的话,并发线程最多为10个
// dispatch_semaphore_t sema = dispatch_semaphore_create(10);
//如果设置为1的话,并发线程为1个,可以保证数据安全
dispatch_semaphore_t sema = dispatch_semaphore_create(1);
dispatch_queue_t gloabQueue = dispatch_get_global_queue(0, 0);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
for (int i = 0; i < 100; i++) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(gloabQueue, ^{
NSLog(@"thread%@ add %d",[NSThread currentThread] ,i);
[array addObject:@(i)];
dispatch_semaphore_signal(sema);
});
}
11. dispatch_once
dispatch_once
函数是保证在应用程序执行中执行一次指定处理的 API。
使用dispatch_once
函数生成单利,即使在多线程情况下执行,也可保证百分百安全。
static dispatch_once_t pred;
dispatch_once(&pred, ^{
NSLog(@"只会执行一次");
});