一、什么是GCD
GCD是Grand Central Dispatch
的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
二、GCD的使用
首先看下这段代码:
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
上面的这段代码是一个简单的异步任务,通过这段代码,引出了下面的几个名词:
1、async(异步)与sync(同步):
具体概念:iOS多线程(一)
当然,我们也可以使用同步任务,使用dispatch_sync函数添加到相应的队列中,而这个函数会阻塞当前调用线程,直到相应任务完成执行。
但是,也正因为这样的同步特性,在实际项目中,当有同步任务添加到正在执行同步任务的队列时,串行的队列会出现死锁。而且由于同步任务会阻塞主线程的运行,可能会导致某个事件无法响应。
2、队列(queue):
队列的基本原理:先进先出(FIFO),先进队列的元素先出队列。
在GCD中,可以给开发者调用的常见公共队列有以下两种:
dispatch_get_global_queue:用于获取应用全局共享的并发队列 (提供多个线程来执行任务,所以可以按序启动多个任务并发执行。可用于后台执行任务)
dispatch_get_main_queue:用于获取应用主线程关联的串行调度队列(只提供一个线程执行任务。运行的main主线程,一般用于UI的搭建)
这两种公共队列的调用便可以解决关于后台执行任务、主线程用于更新UI界面的问题,代码如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 把逻辑计算等需要消耗长时间的任务,放在此处的全局共享的并发队列执行;
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程更新UI界面;
});
});
异步下载图片
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:@"http://img.51fanxing.com/spec/9579/u_20120110174805627264.jpg"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
2.1、获取主线程队列
dispatch_queue_t queue = dispatch_get_main_queue();
Paste_Image.png
2.2、获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Paste_Image.png
通过dispatch_get_global_queue
方法获取的全局队列都是并行队列,并且队列不能被修改。
ps:
identifier
:用以标识队列优先级
flags
:苹果预留的,传入任何非0的值都可能导致返回NULL
2.3、创建队列
dispatch_queue_t queue = dispatch_queue_create("com.51fanxing.queue", NULL);
Paste_Image.png
ps:
label
: 队列的名称,调试的时候可以区分其他的队列attr
: 队列的属性,dispatch_queue_attr_t类型。用以标识队列串行,并行,以及优先级等信息
Paste_Image.png
DISPATCH_QUEUE_SERIAL或者NULL,表示创建串行队列,优先级为目标队列优先级。
DISPATCH_QUEUE_CONCURRENT表示创建并行队列,优先级也为目标队列优先级。
dispatch_queue_attr_make_with_qos_class函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。
attr
: 传入DISPATCH_QUEUE_SERIAL、NULL或者DISPATCH_QUEUE_CONCURRENT,表示串行或者并行qos_class
: 传入qos_class枚举,表示优先级级别relative_priority
: 相对于qos_class的相对优先级,qos_class用于区分大的优先级级别,relative_priority表示大级别下的小级别。relative_priority必须大于QOS_MIN_RELATIVE_PRIORITY小于0,否则将返回NULL。从GCD源码中可以查到QOS_MIN_RELATIVE_PRIORITY等于-15。
2.4、串行队列和并行队列
串行队列指同一时间每次只能执行一个任务。线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。可以添加多个任务到串行队列中,执行顺序按照先进先出(FIFO),如果需要并发地执行大量任务,应该把任务提交到全局并发queue来完成才能更好地发挥系统性能。
利用dispatch_queue_create
函数创建串行queue,两个参数分别是queue名和一组queue属性:
dispatch_queue_t queue = dispatch_queue_create("com.51fanxing.queue", NULL);
Paste_Image.png
从以上代码中可以看出,后面所添加的任务也必须等待前面的任务完成后才能执行,类似我们前面所讲”饭堂”排队的例子,队列完全按照”先进先出”的顺序,也即是所执行的顺序取决于:开发者将工作任务添加进队列的顺序。
并行队列可以同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。
并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出(FIFO)的顺序来启动任务。但是任务结束的顺序则依赖各自的任务所需要消耗的时间。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等。与串行队列的不同,虽然启动时间一致,但是这是“并发执行”,因此不需要等到上一个任务完成后才进行下一个任务。并发queue会在之前的任务完成之前就出列并开始执行下一个任务。
Paste_Image.png
从以上代码中可以看出,与串行不同的是,不需要等到A任务调用完,就已经在调用B、C,显著地提高了线程的执行速度,凸显了并行队列所执行的异步操作的并行特性;
另外,从这段代码中,不同的是串行队列需要创建一个新的队列,而并行队列中,只需要调用iOS系统中为我们提供的全局共享dispatch_get_global_queue就可以了:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3、dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
需要注意的是,使用dispatch_after实现延迟执行某动作,时间并不是很精确,因为main dishpatch queue在主线程的runLoop中执行,所以比如在每隔1/60秒执行的RunLoop中,block最快在三秒后执行,最慢在3秒+1/60秒后执行,并且在main dishpatch queue有大量追加处理货主线程本身的任务处理有延迟时,这个时间会增加。
如果对时间的精确度没有高要求,只是为了推迟执行,那么使用dispatch_after还是很不错的。
ps:
1、NSObject中提供的线程延迟方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
2、通过NSTimer来延迟线程执行
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
4、dispatch_once
一般我们会利用dispatch_once
创建单例
从上面代码中可以看出
第一个参数predicate,该参数是检查后面第二个参数所代表的代码块是否被调用的谓词,
第二个参数则是在整个应用程序中只会被调用一次的代码块。dispach_once函数中的代码块只会被执行一次,而且还是线程安全的。
5、dispatch_apply
从上面代码中可以看出,这些迭代是并发执行的
和普通for循环一样,
dispatch_apply
和dispatch_apply_f
函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。
6、dispatch_group_t,dispatch_group_notify
可以使用dispatch_group_async函数将多个任务关联到一个dispatch group和相应的queue中,group会并发地同时执行这些任务。而且dispatch group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
Paste_Image.png
7、dispatch_barrier_async
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用dispatch_barrier_async
函数将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成。
有时候我们会需要这样的一个场景,A任务和B任务执行完毕之后,在执行C任务,需要借助dispatch_barrier_async这个函数。
Paste_Image.png Paste_Image.png
从代码中可以看出确实只有在前面A、B任务完成后,barrier任务才能执行,最后才能执行C任务。
注意:
使用
dispatch_barrier_async
,该函数只能搭配自定义并行队列dispatch_queue_t
使用。不能使用:dispatch_get_global_queue
,否则dispatch_barrier_async
的作用会和dispatch_async
的作用一模一样。
8、信号量
个人理解,在多线程下使用信号量可以控制多线程的并发数目。
创建信号量,可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
通知信号,如果等待线程被唤醒则返回非0,否则返回0。
dispatch_semaphore_signal(semaphore);
比如,执行10个任务,然后等待2秒,然后继续执行。
Paste_Image.png
网友评论