本文摘录自《Objective-C高级编程》一书,附加一些自己的理解,作为对GCD的总结。
此篇主要包含以下几个方面:
- dispatch_async / dispatch_sync
- dispatch_after
-
dispatch_apply
dispatch_async(异步) / dispatch_sync(同步)
添加方式 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
异步(async) |
有开启新线程 并发执行任务 |
有开启新线程 串行执行任务 |
没有开启新线程 串行执行任务 |
注:所谓开启新线程是指任务在主线程之外的线程中执行,即产生了主线程以外的新线程
dispatch_async
函数的“async”意味着“非同步”(asynchronous),就是将指定的Block“非同步”地追加到指定的Dispatch Queue中。dispatch_async
函数不做任何等待。
dispatch_sync
函数意味着“同步”(synchronous),也就是将指定的Block“同步”追加到指定的Dispatch Queue中。在追加Block结束之前,dispatch_sync
函数会一直等待。一旦调用dispatch_sync
函数,那么在指定的处理执行结束之前,该函数不会返回。
下面我们用实际案例逐一体验:
同步函数+并发队列
- (void)viewDidLoad {
[super viewDidLoad];
[self sync_concurrent];
}
- (void)sync_concurrent {
dispatch_queue_t queue = dispatch_queue_create("com.example.sync_concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
});
}
控制台:
总结:
- 没有开启新线程
- 串行执行任务
同步函数+串行队列
- (void)viewDidLoad {
[super viewDidLoad];
[self sync_serial];
}
- (void)sync_serial {
dispatch_queue_t queue = dispatch_queue_create("com.example.sync_serial", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
});
}
控制台:
总结:
- 没有开启新线程
- 串行执行任务
同步函数+主队列
- (void)viewDidLoad {
[super viewDidLoad];
[self sync_mainQueue];
}
- (void)sync_mainQueue {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
});
}
😎
😎😎
😎😎😎 此处线程死锁,各位客官可以移步iOS中的线程死锁,看个究竟。
😎😎
😎
异步函数+并发队列
- (void)viewDidLoad {
[super viewDidLoad];
[self async_concurrent];
}
- (void)async_concurrent {
dispatch_queue_t queue = dispatch_queue_create("com.example.async_concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
});
}
控制台:
总结:
- 有开启新线程
- 并发执行任务
异步函数+串行队列
- (void)viewDidLoad {
[super viewDidLoad];
[self async_serial];
}
- (void)async_serial {
dispatch_queue_t queue = dispatch_queue_create("com.example.async_serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
sleep(1);
});
dispatch_async(queue, ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
sleep(1);
});
}
控制台:
总结:
- 有开启新线程
- 串行执行任务
异步函数+主队列
- (void)viewDidLoad {
[super viewDidLoad];
[self async_mainQueue];
}
- (void)async_mainQueue {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务1 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务2 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务3 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务4 当前线程:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务5 当前线程:%@",[NSThread currentThread]);
});
}
控制台:
总结:
- 没有开启新线程
- 串行执行任务
dispatch_after(延迟执行)
经常会有这样的情况:想在3秒后执行处理。可能不仅限于3秒,总之,这种想在指定时间后执行处理的情况,可使用dispatch_after
函数来实现。
在3秒后将指定的Block 追加到Main Dispatch Queue中的源代码如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"wait at least three seconds.");
});
/*
* 或者使用下面这种方式
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"wait at least three seconds.");
});
需要注意的是,dispatch_after
函数并不是在指定时间后执行处理,而只是在指定时间后追加处理到Dispatch Queue。此源代码与在3秒后用dispatch_async
函数追加Block到Main Dispatch Queue的相同。
因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
虽然在有严格时间的要求下使用时会出现问题,但在想大致延迟执行处理时,该函数是非常有效的。
另外,第二个参数指定要追加处理的Dispatch Queue,第三个参数指定记述要执行处理的Block。
第一个参数是指定时间用的dispatch_time_t
类型的值。该值使用dispatch_time
函数或dispatch_walltime
函数作成。
dispatch_time
函数能够获取从第一个参数dispatch_time_t
类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是之前源代码中出现的DISPATCH_TIME_NOW
。这表示现在的时间。即以下源代码可得到表示从现在开始1秒后的dispatch_time_t
类型的值。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
dispatch_walltime
函数由POSIX中使用的struct timespec 类型的时间得到dispatch_time_t
类型的值。dispatch_time
函数通常用于计算相对时间,而dispatch_walltme
函数用于计算绝对时间。例如在dispatch_after
函数中想指定2011年11月1日11时11分1秒这一绝对时间的情况,这可作为粗略的闹钟功能使用。
struct timespec类型的时间可以很轻松地通过NSDate
类对象作成。
dispatch_apply(重复执行)
dispatch_apply
函数是dispatch_sync
函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
举例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu\t当前线程:%@",index,[NSThread currentThread]);
});
NSLog(@"done");
控制台:
说明:
因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定。但是输出结果中最后的done必定在最后的位置上。这是因为dispatch_apply
函数会等待全部处理执行结束。
第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理。与其它的函数不同,第三个参数的Block为带有参数的Block。这是为了按第一个参数重复追加Block并区分各个Block而使用。
总结:
-
dispatch_apply
函数的执行会阻塞线程,所以在循环10次结束后才会打印done -
dispatch_apply
函数执行任务时开启了新线程,有主线程参与也有其它线程参与,属于多线程执行 -
因为指定的队列是Global Dispatch Queue并发队列,所以
dispatch_apply
并发执行了任务。若把Global Dispatch Queue换成Main Queue随即成为死锁并报错。由此可见,dispatch_apply
函数添加任务到队列与dispatch_sync
函数原理相仿,即同步添加任务,这就解释了前两点阻塞线程和多线程执行。
举一个实际例子:
要对NSArray
类对象的所有元素执行处理时,不必一个一个编写for
循环,参考以下源代码
NSArray *arr = @[@"一",@"二",@"三",@"四",@"五",@"六",@"七",@"八",@"九",@"十"];
dispatch_apply(arr.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%@\t当前线程:%@",[arr objectAtIndex:index],[NSThread currentThread]);
});
控制台:
这样可简单地在Global Dispatch Queue中对所有元素执行Block。
另外,由于dispatch_apply
函数也与dispatch_sync
函数相同,会等待处理执行结束,因此推荐在dispatch_async
函数中非同步地执行dispatch_apply
函数。
NSArray *array = [NSArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 在Global Dispatch Queue中非同步执行
*/
dispatch_async(queue, ^{
/*
* Global Dispatch Queue
* 等待dispatch_apply函数中全部处理执行结束
*/
dispatch_apply([array count], queue, ^(size_t index) {
/*
* 并列处理包含在NSArray对象的全部对象
*/
NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
});
/*
* dispatch_apply函数中的处理全部结束
*/
/*
* 在Main Dispatch Queue中非同步执行
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* 在Main Dispatch Queue执行处理
* 用户界面更新等
*/
NSLog(@"done");
});
});
网友评论