第一部分
GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式。 在Mac OS X 10.6和IOS 4.0之后开始支持GCD。
什么是 GCD
GCD 是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:
1.GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
2.GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
3.GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
使用GCD的一个理由就是方便。回想一下以前的多线程编程,我们会把异步调用的代码放到另外的一个函数中,并通过NSThread开启新线程来启动这段代码。 这种跳来跳去的流程对于复杂的逻辑简直就是一场灾难。更糟糕的是,调用线程时的环境对异步代码是不可见的,如果我们需要当时的临时变量的话只有两个选择: 保存到类成员变量中或者作为参数传递过去。前者会造成很多莫名奇妙的无关类成员,而后者的功能过于有限。
GCD相对来说是一种更优雅的方式,看如下代码:
NSString* parameter = [self getSomeParameter];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString* result = [self fetchResultFromWebWithParameter:parameter];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIWithResult:result];
});
});
在上面的代码中,出现了一种奇怪的格式:
^{code…}
解释一下,当一段代码被花括号包裹并在开头放置上尖号时,我们称之为块(block)。如果你学过C语言的话(实际上,block正是apple对C的一个扩展), 你可以认为这是一个增强型的函数指针。它不仅可以当做一个变量来回传递,还可以引用本身环境之外的变量(如上面代码中的parameter)。 更进一步地说,它是apple的C扩展中闭包的实现。在block里引用的对象会自动被retain,因此你也不必担心内存的问题。
另外涉及到了三个函数
void dispatch_async(
dispatch_queue_t queue,
dispatch_block_t block);
dispatch_queue_t dispatch_get_global_queue(
long priority,
unsigned long flags);
dispatch_get_main_queue();
dispatch_async 函数会将传入的block块放入指定的queue里运行。这个函数是异步的,这就意味着它会立即返回而不管block是否运行结束。因此,我们可以在block里运行各种耗时的操作(如网络请求) 而同时不会阻塞UI线程。
dispatch_get_global_queue 会获取一个全局队列,我们姑且理解为系统为我们开启的一些全局线程。我们用priority指定队列的优先级,而flag作为保留字段备用(一般为0)。
dispatch_get_main_queue 会返回主队列,也就是UI队列。它一般用于在其它队列中异步完成了一些工作后,需要在UI队列中更新界面(比如上面代码中的[self updateUIWithResult:result])的情况。
好的,知道这些特性之后,我们可以这样理解上面的代码:利用parameter变量异步地发起一个网络请求,并在请求之后更新UI线程。
GCD Queue 分为三种:
1,The main queue :主队列,主线程就是在个队列中。
2,Global queues : 全局并发队列。
3,用户队列:是用函数 dispatch_queue_create 创建的自定义队列
第二部分:
dispatch_sync 和 dispatch_async 区别:
dispatch_async(queue,block) async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
dispatch_sync(queue,block) sync 同步队列,dispatch_sync 函数不会立即返回,即阻塞当前线程,等待 block同步执行完成。
下面举例说明一下 dispatch_sync 和 dispatch_async的一些使用注意事项:
dispatch_sync(dispatch_get_main_queue(), ^(){
NSLog(@"test");
});
讲解:
在ios中是无法使用 dispatch_sync(dispatch_get_main_queue()
原因如下:
在ios使用 dispatch_sync(dispatch_get_main_queue()^(){block体});
dispath向主队列加一个同步的block;此时主队列在等待 dispatch_sync(dispatch_get_main_queue(),^(){block体});执行
dispatch_sync在等待主队列执行完毕。
造成死锁。
所以在ios使用主队列 dispatch_get_main_queue() 时 应该使用异步执行
dispatch_async(dispatch_get_main_queue(), ^(){
NSLog(@”222”);
});
dispatch_sync
则调用用 dispatch_sync的线程会等 dispatch_sync的对内容执行完再继续执行。
dispatch_async
调用dispatch_async的线程不会的等dispatch_async的内容,自己继续执行。
dispatch_sync/async(dispatch_get_main_queue(),^(){block体})
只是将block加入到队列。执行顺序按照队列顺序
sync/async的区别在于 调用diapatch的线程是否等待dispatch执行完。
下面来看下如何使用GCD编程的异步dispatch_async
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理耗时操作的代码块...
//通知主线程刷新
dispatch_async(dispatch_get_main_queue(), ^{
//回调或者说是通知主线程刷新,
});
});
dispatch_async开启一个异步操作,第一个参数是指定一个gcd队列,第二个参数是分配一个处理事物的程序块到该队列。
dispatch_get_global_queue(0, 0),指用了全局队列。
一般来说系统本身会有3个队列。
global_queue,current_queue,以及main_queue.
获取一个全局队列是接受两个参数,第一个是我分配的事物处理程序块队列优先级。分高低和默认,0为默认2为高,-2为低
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
处理完事物后,需要将结果返回给或者是刷新UI主线程,同样,和上面一样,抓取主线程,程序块操作。
第三部分:
其实对于编程中,我们一直提及到的几个概念,同步,异步,并发,锁等。
下面我们以上面提到的图片加载来看下这3个概念我的理解:
1、同步:
for (int i = 0 ; i < 10; i++) {
UIImage *img = [self getImgeWith:[urlArr objectForIndex:i]];
[myImgV[i] setImage:img];
}
假设我要加载10个图片,我现在拥有这些图片的资源地址,保存在一个数组中。
我们先以获取第一张图片来举例:
同步执行的概念就是,我获取完第一张图片的,
执行了for循环第一句返回了img后,我才能执行第二句,UI界面的刷新。
如果第一句返回的时间需要10秒,那我程序的响应就仿佛一直卡在这里一样,我无法进行其他操作。必须等它返回。
因此,同步的一个很好理解的感念就是,一步走到黑。
2.异步
for (int i = 0 ; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理耗时操作的代码块...
UIImage *img = [self getImgeWith:[urlArr objectForIndex:i]];
//通知主线程刷新
dispatch_async(dispatch_get_main_queue(), ^{
//回调或者说是通知主线程刷新,
[myImgV[i] setImage:img];
});
});
看了这代码,我们会说,异步操作那个假设还是要10秒啊,总体看来,执行一张图片的时间加载还是要在10秒左右啊,
貌似异步没什么鸟用么。但是,别忽略了其中一点,也黑丝核心的一点,此时我们图片获取操作放在里一个线程队列里,
此刻,虽然我们看着图片的加载还是需要10秒才会出来,但是,在这10秒期间,我们的UI主线程是可以操作的,比如界面上有个按钮,你是可以按的
而不是如上面的同步,在10面期间,我是只能干等着,什么都做不了。
异步的核心概念就是一个新线程,一个消息回调通知。
3.并行
我们还是以上代码为例。前面我强调了,我们只看一张图片的加载,现在,回到我们第一眼看到代码的思维上去,
一个for循环。其实上面代码过后,我是创建了10个异步线程。
好吧,到此,我们应该明白这三个概念了。
同步,其实我前面的例子举得有些局限,就是这个例子本身就说明不需要同步执行,然后给大家大感觉是
同步是编程中一个忌讳点一样,其实不然,很多时候。我们真是需要同步来做一些限制(比如线程中提出的同步锁?听着就感觉有用么
虽然可能并不如我们想的那样的运用同步,但是至少说明这个概念同样是有用的)
我还是以刚才那个加载图片为例子,来个简单的说明如何运用同步的好处。
当然,我只是模拟一个同步的情况。
假设我们现在图片的加载是这样的,图片本身为在加载前是一个默认的图片,上面写着,点击我加载,点击后会调用网络加载方法,然后图片显示加载中,
然后我们双击图片时(当然,理论上是在加载完后)读取图片网络图片放大,好吧,到这里应该能想到要表达的情况了。
整个流程应该是点击图片->加载->双击查看。那如果成了点击->加载中(以返回了图片的作者和信息)-》双击图片(通过前面请求返回的大图链接显示大图)-》
完全加载返回(返回了大图链接)。此时我们看不到图像的大图了。因为我们操作在返回前了,也就是说,
很多时候,我们下一个动作的操作必须需要用到前面一个操作的数据时,我们会给他做认为的同步编程,比如加个按钮锁。
这是我们又会疑惑道,下一个执行需要用到前一个执行的,那第一个例子中的for循环的第二句不是要用到么,这么说
他们必须要同步啊,如果你这么想了,好巧,我们想到一块去了~
但是,注意,前面我们到的异步是为了解决我点击其他按钮的操作,而不是说更新UI操作。下载和更新UI操作在我们看来必须是同步的
这是对的,但是那种做导致了系统本身一些监听事件监听到点击处理在那个请求之后了,这边的加载图片其实要看成一次事件执行,
因为对于事件的这一抽象单元,其实是一种可人为定义的宽广度。
也就是说,一次数据获取和图像填充,其实算是一个图像获取加载事件,事件可以说包含两个单元,加载和填充。
而整个这个事件对于我们点击其他按钮并无关系,那么也就说明了无需同步。
有道理啊,但是若果我们要点击这个图片呢,也就是回到刚才那个可以双击的假设。
此处也许我么又忽略了一点为什么加载中我们能点击双击呢,也就这样的假设是获取图片已经做了异步,但是我们下一步操作又是需要同步的
因此做了人为的同步锁定。
好了,说的太多了,当时至少我们明白两点
异步可能是为了反正耗时操作造成的主线程堵塞,
同步是为了解决一些不必要错误和麻烦。也许到这里,我们脑中会联想到的所谓的线程安全性。
其实同步以及同步锁,却是应该是考虑到这样的不必要和不安全因素。
最后在简单阐述下异步和并发关系。
其实看了上面说的,异步只是提供了一种多线程处理的概念,
并发是更像是异步的一种大规模实现。
就好比说,异步提出了可以用小弟去收保护费,收完了告诉并交给自己,而我在期间做其他要做的事。
并发突然想到,异步这个很有道理啊,那我有4个地方要收,一个小弟去收,虽然我还是可以闲着做其他的事,
但是小弟跑四个地方,我拿到钱所需要的时间还是和我自己去收一样的,只不过我不用那么费劲了,还能做其他事了。
因此,并发觉得应该派四个小弟去,因为每个场地的保护费各不相干的。(刚看了个纽约黑帮~)。
因此说,异步解决了线程堵塞,而并发则是在异步的基础上,提高了符合特性事件的处理时间效率。
当然,如果10个图片本身相互间是没什么联系,但是,最后一个事件需要处理计算这10个图片的总容量值。
那么可以用 dispatch_group_async。
具体就看文档吧。
参考链接:
http://www.2cto.com/kf/201507/415322.html
http://blog.csdn.net/nono_love_lilith/article/details/7829557