ObjC-多线程之GCD
介绍
GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。GCD 是一套低层API,基于C语言开发,完全面向过程的,用于将任务切分成单一任务提交至队列并发或者串行执行。遵循FIFO原则,先提交到队列的先执行。
iOS4.0中首度引入GCD,GCD是管理任务执行的一项技术,它使得我们对多任务处理变得更加方便和有效。它支持同步或异步任务处理,串行或并行的处理队列(Dispath Queue),非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等。这个庞大的任务处理技术大大减少了线程的管理工作,使基于任务的开发变得更加高效。
GCD 和 block (<http://scottmaxiao.github.io/IOS-Block.html>)的配合使用,可以方便地进行多线程编程。
基本概念
串行和并行
串行:任务按先后顺序逐个执行。
并行:后面的任务不会等前面的任务完成了再执行,同样会遵循先添加先执行的原则,但添加间隔往往忽略不计。所以看上去像是一起执行。
并发和并行
并发是指两个或多个事件在同一时间间隔内发生。单核CPU并发切换执行多个任务。
并行是指两个或者多个事件在同一时刻发生。多核CPU可以平行执行多个任务。
下图描述的就是并发和并行的区别。
gcd同步和异步
同步:一个同步函数只在完成了预定任务后才返回。会阻塞当前线程。
异步:异步时任务开启会立即返回,不阻塞当前线程去执行下一个函数。异步会开启其他线程。
函数说明
Dispatch Queue
Dispatch Queue是用来执行任务的队列,是GCD中最基本的元素之一。
Dispatch Queue分为两种:
- Serial Dispatch Queue,按添加进队列的顺序(先进先出)一个接一个的执行
- Concurrent Dispatch Queue,并发执行队列里的任务
简而言之,Serial Dispatch Queue只使用了一个线程,Concurrent Dispatch Queue使用了多个线程(具体使用了多少个,由系统决定)。
手动创建Dispatch Queue
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_CONCURRENT);
获取系统提供的Dispatch Queue
系统提供的Dispatch Queue有两种类型
-
Main Dispatch Queue:
实际上就是Serial Dispatch Queue(并且只有一个),一般只在需要更新UI时我们才获取Main Dispatch Queue -
Global Dispatch Queue:
实际上是一个Concurrent Dispatch Queue。大多数情况下,可以不必通过dispatch_queue_create函数生成Concurrent Dispatch Queue,而是只要获取Global Dispatch Queue使用即可。Global Dispatch Queue有4个优先级,分别是:High、Default、Low、Background。
//获取Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue()
//获取Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
需要注意一点,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Queue将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放,如下:
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", nil);
dispatch_async(queue, ^{
...
});
dispatch_release(queue) ;
dispatch_async和dispatch_sync,dispatch_barrier_async
dispatch_async()
dispatch_async():异步添加进任务队列,它不会做任何等待。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
[NSThread sleepForTimeInterval:5];
NSLog(@"3");
});
NSLog(@"4");
输出
11:42:43.820 GCDSeTest[568:303] 1
11:42:43.820 GCDSeTest[568:303] 4
11:42:43.820 GCDSeTest[568:1003] 2
11:42:48.821 GCDSeTest[568:1003] 3//模拟长时间操作时间
dispatch_sync()
dispatch_sync():同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(concurrentQueue, ^(){
NSLog(@"2");
[NSThread sleepForTimeInterval:10];
NSLog(@"3");
});
NSLog(@"4");
输出
11:36:25.313 GCDSeTest[544:303] 1
11:36:25.313 GCDSeTest[544:303] 2
11:36:30.313 GCDSeTest[544:303] 3//模拟长时间操作
11:36:30.314 GCDSeTest[544:303] 4
关于死锁:
//在main线程使用“同步”方法提交Block,必定会死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"I am block...");
});
dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁
dispatch_barrier_async
dispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。一个典型的例子就是数据的读写,通常为了防止文件读写导致冲突,我们会创建一个串行的队列,所有的文件操作都是通过这个队列来执行,比如FMDB,这样就可以避免读写冲突。不过其实这样效率是有提升的空间的,当没有更新数据时,读操作其实是可以并行进行的,而写操作需要串行的执行,如何实现呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"reading data1");
});
dispatch_async(queue, ^{
NSLog(@"reading data2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"writing data1");
[NSThread sleepForTimeInterval:1];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"reading data3");
});
我们将写数据的操作放在dispatch_barrier_async中,这样能确保在写数据的时候会等待前面的读操作完成,而后续的读操作也会等到写操作完成后才能继续执行,提高文件读写的执行效率。
dispatch_apply
dispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回,循环迭代的执行顺序是不确定的。
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并发的运行一个block任务5次
dispatch_apply(5, queue, ^(size_t i) {
NSLog(@"do a job %zu times",i+1);
});
NSLog(@"go on");
Dispatch Block
添加到gcd队列中执行的任务是以block的形式添加的,block封装了需要执行功能,block带来的开发效率提升就不说了,gcd跟block可以说是一对好基友,能够很好的配合使用。
创建 block
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//创建block
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"do something");
});
dispatch_async(queue, block);
----
在创建block的时候我们也可以通过设置QoS,指定block对应的优先级,在dispatch_block_create_with_qos_class中指定QoS类别即可:
----
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
NSLog(@"do something with QoS");
});
dispatch_async(queue, block);
dispatch_block_wait
当需要等待前面的任务执行完毕时,我们可以使用dispatch_block_wait这个接口,设置等待时间DISPATCH_TIME_FOREVER会一直等待直到前面的任务完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"before sleep");
[NSThread sleepForTimeInterval:1];
NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任务执行完毕
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue");
dispatch_block_notify
dispatch_block_notify当观察的某个block执行结束之后立刻通知提交另一特定的block到指定的queue中执行,该函数有三个参数,第一参数是需要观察的block,第二个参数是被通知block提交执行的queue,第三参数是当需要被通知执行的block,函数的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
dispatch_block_t notification_block);
-----
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t previousBlock = dispatch_block_create(0, ^{
NSLog(@"previousBlock begin");
[NSThread sleepForTimeInterval:1];
NSLog(@"previousBlock done");
});
dispatch_async(queue, previousBlock);
dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
NSLog(@"notifyBlock");
});
//当previousBlock执行完毕后,提交notifyBlock到global queue中执行
dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);
dispatch_block_cancel
之前在介绍nsopreration的时候提到它的一个优点是可以取消某个operation,现在在iOS8之后,提交到gcd队列中的dispatch block也可取消了,只需要简单的调用dispatch_block_cancel传入想要取消的block即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"block1 begin");
[NSThread sleepForTimeInterval:1];
NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block2);
dispatch_semaphore
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
http://www.cnblogs.com/zhidao-chen/p/3600399.html
http://blog.csdn.net/fhbystudy/article/details/25918451
http://www.w2bc.com/Article/16104
http://www.mamicode.com/info-detail-1218654.html
dispatch_after()
Dispatch Queue的延迟执行。
// 延迟 2 秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
// code to be executed on the main queue after delay
});
这里要说一下dispatch_time函数,其原型如下:
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
-
第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。
-
第二个参数就是真正的延时的具体时间。这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000”=。=,太长了,所以理所当然系统提供了常量,如下:
#define NSEC_PER_SEC 1000000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
关键词解释:
NSEC:纳秒。
USEC:微妙。
SEC:秒
PER:每
所以:
NSEC_PER_SEC,每秒有多少纳秒。
USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
NSEC_PER_USEC,每毫秒有多少纳秒。
[参考]http://www.cocoachina.com/ios/20150505/11751.html
dispatch_once
dispatch\_once是线程安全的。它能保证多个线程同时调用却只会执行块一次。在dispatch\_once返回之前,所有线程将会等待直到执行完毕。
dispatch\_once函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次,如果我们要通过dispatch\_once创建一个单例类。
+(id)getInstance{
static GapSet *gapSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gapSet = [[GapSet alloc] initObject];
});
return gapSet;
}
注意:dispatch_once_t必须是全局或static变量。
[参考]
http://blog.afantree.com/gcd/translation-dispatch-once-secret.html
dispatch_suspend和dispatch_resume
http://www.jianshu.com/p/85b75c7a6286
dispatch_group
如果想在dispatch\_queue中所有的任务执行完成后在做某种操作,在串行队列中,可以把该操作放到最后一个任务执行完成后继续,但是在并行队列中怎么做呢。这就有dispatch\_group 成组操作。比如
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 并行执行的线程一
NSLog(@"dispatch-1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 并行执行的线程二
NSLog(@"dispatch-2");
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
// 汇总结果
NSLog(@"dispatch-end");
});
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
Dispatch IO
Dispatch Source
http://www.jianshu.com/p/f9e01c69a46f
[参考]
http://www.jianshu.com/p/f9e01c69a46f
http://blog.devtang.com/blog/2012/02/22/use-gcd/
http://blog.csdn.net/zhangao0086/article/details/38904923
http://www.cocoachina.com/ios/20150505/11751.html
http://www.cnblogs.com/SnailFish/articles/3199863.html
http://www.cnblogs.com/zhidao-chen/category/461198.html
http://blog.afantree.com/ios/the-gcd-learning-directorygcd-debrief.html