gcd68067部队日美白用什么方法

study(14)
介绍(?一)基本概念和
什么是GCD?
Grand Central Dispatch或者GCD,是?一套低层API,提供了?一种新的?方法来进?行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将
任务切分为多个单?一任务然后提交?至?工作队列来并发地或者串?行地执?行。GCD?比之NSOpertionQueue更底层更?高效,并且它不是Cocoa框架的?一部分。除了代码的平?行执?行能?力,GCD还提供?高度集成的事件控制系统。可以设置句柄来响应?文件描述符、mach
ports(Mach port
OS X上的进程间通讯)、进程、计时
器、信号、?用户?生成事件。这些句柄通过GCD来并发执?行。GCD的API很?大程度上基于block,当然,GCD也可以脱离block来使?用,?比如使?用传统c机制提供函数指针和上下?文指针。实践证明,当配合block使?用时,GCD?非常简
单易?用且能发挥其最?大能?力。
你可以在Mac上敲命令“man
dispatch”来获取GCD的?文档。为何使?用?
GCD提供很多超越传统多线程编程的优势:
GCD?比之thread跟简单易?用。由于GCD基于work
unit?而?非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视?文件描述符、周期执?行代码以及?工作挂起等任务。基于block的?血统导致它能极为简单得在不同代码作?用域之间传递上下?文。
GCD被实现得如此轻量和优雅,使得它在很多地?方?比之专?门创建消耗资源的线程更实?用且快速。这关系到易?用性:导致GCD易?用的原因有?一部分在于你可以不?用担⼼心太多的效率问题?而仅仅使?用它就?行了。
GCD?自动根据系统负载来增减线程数量,这就减少了上下?文切换以及增加了计算效率。
Dispatch Objects
尽管GCD是纯c语?言的,但它被组建成?面向对象的?风格。GCD对象被称为dispatch
object。Dispatch object像Cocoa对象?一样是引?用计数的。使?用dispatch_release和dispatch_retain函数来操作dispatch
object的引?用计数来进?行内存管理。但注意不像Cocoa对象,dispatch
object并不参与垃圾回收系统,所以即使开启了GC,你也必须?手动管理GCD对象的内存。
Dispatch queues
dispatch sources(后?面会介绍到)可以被挂起和恢复,可以有?一个相关联的任意上下?文指针,可以有?一个相关联的任务完成触发函数。可以查阅“man
dispatch_object”来获取这些功能的更多信息。
Dispatch Queues
GCD的基本概念就是dispatch
queue。dispatch queue是?一个对象,它可以接受任务,并将任务以先到先执?行的顺序来执?行。dispatch
queue可以是并发的或串?行的。并发任务会像NSOperationQueue那样基于系统负载来合适地并发进?行,串?行队列同?一时间只执?行单?一任务。
GCD中有三种队列类型:
The main queue: 与主线程功能相同。实际上,提交?至main queue的任务会在主线程中执?行。main
queue可以调?用dispatch_get_main_queue()来获得。因为mainqueue是与主线程相关的,所以这是?一个串?行队列。
Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:?高、中(默认)、低、后台四个优先级队列。可以调?用dispatch_get_global_queue函数传?入优先级来访问队列。优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
?用户队列:
?用户队列 (GCD并不这样称呼这种队列,
但是没有?一个特定的名字来形容这种队列,所以我们称其为?用户队列)
是?用函数 dispatch_queue_create
创建的队列.
这些队列是串?行的。正因为如此,它们可以?用来完成同步机制,
有点像传统线程中的mutex。
要使?用?用户队列,我们?首先得创建?一个。调?用函数dispatch_queue_create就?行了。函数的第?一个参数是?一个标签,这纯是为了debug。Apple建议我们使?用倒置域名来命名队列,?比如“com.dreamingwish.subsystem.task”。这些名字会在崩溃?日志中被显?示出来,也可以被调试器调?用,这在调试中会很有?用。第?二个参数??目前还不?支持,传?入NULL就?行了。
Job向?一个队列提交Job很简单:调?用dispatch_async函数,传?入?一个队列和?一个block。队列会在轮到这个block执?行时执?行这个block的代码。下?面的例?子是?一个在后台执
?行?一个巨?长的任务:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongAndInvolved];
NSLog(@&Done doing something long and involved&);
码。你可以简单地完成这个任务——使?用嵌套的dispatch,在外层中执?行后台任务,在内层中将任务dispatch到main
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongAndInvolved];
dispatch_async(dispatch_get_main_queue(), ^{
[textField setStringValue:@&Done doing something long and involved&];
还有?一个函数叫dispatch_sync,它干的事?儿和dispatch_async相同,但是它会等待block中的代码执?行完成并返回。结合
__block类型修饰符,可以?用来从执?行中的block获取?一个值。例如,你可能有?一段代码在后台执?行,?而它需要从界?面控制层获取?一个值。那么你可以使?用dispatch_sync简单办到:
__block NSString *stringV
dispatch_sync(dispatch_get_main_queue(), ^{
// __block variables aren't automatically retained
// so we'd better make sure we have a reference we can keep
stringValue = [[textField stringValue] copy];
[stringValue autorelease];
// use stringValue in the background now
我们还可以使?用更好的?方法来完成这件事——使?用更“异步”的?风格。不同于取界?面层的值时要阻塞后台线程,你可以使?用嵌套的block来中?止后台线程,然后从主线程中获取值,然后再将后期处理提交?至后台线程:
dispatch_queue_t bgQueue = myQ
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stringValue = [[[textField stringValue] copy] autorelease];
dispatch_async(bgQueue, ^{
// use stringValue in the background now
取决于你的需求,myQueue可以是?用户队列也可以使全局队列。
不再使?用锁(Lock)?用户队列可以?用于替代锁来完成同步机制。在传统多线程编程中,你可能有?一个对象要被多个线程使?用,你需要?一个锁来保护这个对象:
访问代码会像这样:
- (id)something
[lock lock];
localSomething = [[something retain] autorelease];
[lock unlock];
return localS
- (void)setSomething:(id)newSomething
[lock lock];
if(newSomething != something)
[something release];
something = [newSomething retain];
[self updateSomethingCaches];
[lock unlock];
使?用GCD,可以使?用queue来替代:dispatch_queue_t
要?用于同步机制,queue必须是?一个?用户队列,?而?非全局队列,所以使?用usingdispatch_queue_create初始化?一个。然后可以?用dispatch_async
dispatch_async
函数会?立即返回, block会在后台异步执?行。当然,通常,任务完成时简单地NSLog个消息不是个事?儿。在典型的Cocoa程序中,你很有可能希望在任务完成时更新界?面,这就意味着需要在主线程中执?行?一些代
dispatch_sync将共享数据的访问代码封装起来:
- (id)something
__block id localS
dispatch_sync(queue, ^{
localSomething = [something retain];
return [localSomething autorelease];
- (void)setSomething:(id)newSomething
dispatch_async(queue, ^{
if(newSomething != something)
[something release];
something = [newSomething retain];
[self updateSomethingCaches];
值得注意的是dispatch queue是?非常轻量级的,所以你可以?大?用特?用,就像你以前使?用lock?一样。现在你可能要问:“这样很好,但是有意思吗?我就是换了点代码办到了同?一件事?儿。”实际上,使?用GCD途径有?几个好处:
平?行计算:
注意在第?二个版本的代码中,
-setSomething:是怎么使?用dispatch_async的。调?用
-setSomething:会?立即返回,然后这?一?大堆?工作会在后台执?行。如果updateSomethingCaches是?一个很费时费?力的任务,且调?用者将要进?行?一项处理器?高负荷任务,那么这样做会很棒。
使?用GCD,我们就不可能意外写出具有不成对Lock的代码。在常规Lock代码中,我们很可能在解锁之前让代码返回了。使?用GCD,队列通常持续运?行,你必将归还控制权。
使?用GCD我们可以挂起和恢复dispatch
queue,?而这是基于锁的?方法所不能实现的。我们还可以将?一个?用户队列指向另?一个dspatch queue,使得这个?用户队列继承那个dispatch
queue的属性。使?用这种?方法,队列的优先级可以被调整——通过将该队列指向?一个不同的全局队列,若有必要的话,这个队列甚?至可以被?用来在主线程上执?行代码。
GCD的事件系统与dispatch
queue相集成。对象需要使?用的任何事件或者计时器都可以从该对象的队列中指向,使得这些句柄可以?自动在该队列上执?行,从?而使得句柄可以与对象?自动同步。
现在你已经知道了GCD的基本概念、怎样创建dispatch
queue、怎样提交Job?至dispatch
queue以及怎样将队列?用作线程同步。接下来我会向你展?示如何使?用GCD来编写平?行执?行代码来充分利?用多核系统的性能^
^。我还会讨论GCD更深层的东?西,包括事件系统和queue
targeting。
介绍(?二)多核⼼心的性能概念
为了在单?一进程中充分发挥多核的优势,我们有必要使?用多线程技术(我们没必要去提多进程,这玩意?儿和GCD没关系)。在低层,GCD全局dispatch
queue仅仅是?工作线程池的抽象。这些队列中的Block?一旦可?用,就会被dispatch到?工作线程中。提交?至?用户队列的Block最终也会通过全局队列进?入相同的?工作线程池(除?非你的?用户队列的??目标是主线程,但是为了提?高运?行速度,我们绝不会这么干)。
有两种途径来通过GCD“榨取”多核⼼心系统的性能:将单?一任务或者?一组相关任务并发?至全局队列中运算;将多个不相关的任务或者关联不紧密的任务并发?至?用户队列中运算;
设想下?面的循环:
-doSomethingIntensiveWith:
是线程安全的且可以同时执?行多个.?一个array通常包含多个元素,这样的话,我们可以很简单地使?用GCD来平?行运算:
如此简单,我们已经在多核⼼心上运?行这段代码了。
当然这段代码并不完美。有时候我们有?一段代码要像这样操作?一个数组,但是在操作完成后,我们还需要对操作结果进?行其他操作:
for(id obj in array)
[self doSomethingIntensiveWith:obj];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for(id obj in array)
dispatch_async(queue, ^{
[self doSomethingIntensiveWith:obj];
for(id obj in array)
[self doSomethingIntensiveWith:obj];
[self doSomethingWith:array];
这时候使?用GCD的
dispatch_async
就悲剧了.我们还不能简单地使?用dispatch_sync来解决这个问题,
因为这将导致每个迭代器阻塞,就完全破坏了平?行计算。解决这个问题的?一种?方法是使?用dispatch group。?一个dispatch
group可以?用来将多个block组成?一组以监测这些Block全部完成或者等待全部完成时发出的消息。使?用函
数dispatch_group_create来创建,然后使?用函数dispatch_group_async来将block提交?至?一个dispatch
queue,同时将它们添加?至?一个组。所以我们现在可以重新编码:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for(id obj in array)
dispatch_group_async(group, queue, ^{
[self doSomethingIntensiveWith:obj];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
[self doSomethingWith:array];
如果这些?工作可以异步执?行,那么我们可以更?风骚?一点,将函数-doSomethingWith:放在后台执?行。我们使?用dispatch_group_async函数建?立?一个block在组完成后执?行:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for(id obj in array)
dispatch_group_async(group, queue, ^{
[self doSomethingIntensiveWith:obj];
dispatch_group_notify(group, queue, ^{
[self doSomethingWith:array];
dispatch_release(group);
不仅所有数组元素都会被平?行操作,后续的操作也会异步执?行,并且这些异步运算都会将程序的其他部分的负载考虑在内。注意如果-doSomethingWith:需要在主线程中执?行,?比如操作GUI,那么我们只要将main
queue?而?非全局队列传给dispatch_group_notify函数就?行了。
对于同步执?行,GCD提供了?一个简化?方法叫做dispatch_apply。这个函数调?用单?一block多次,并平?行运算,然后等待所有运算结束,就像我们想要的那样:
这很棒,但是异步咋办?dispatch_apply函数可是没有异步版本的。但是我们使?用的可是?一个为异步?而?生的API啊!所以我们只要?用dispatch_async函数将所有代码推到后台就?行了:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index){
[self doSomethingIntensiveWith:[array objectAtIndex:index]];
[self doSomethingWith:array];
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index){
[self doSomethingIntensiveWith:[array objectAtIndex:index]];
[self doSomethingWith:array];
简单的要死!
这种?方法的关键在于确定我们的代码是在?一次对不同的数据?片段进?行相似的操作。如果你确定你的任务是线程安全的(不在本篇讨论范围内)那么你可以使?用GCD来重写你的循环了,更平?行更?风骚。
要看到性能提升,你还得进?行?一?大堆?工作。?比之线程,GCD是轻量和低负载的,但是将block提交?至queue还是很消耗资源的——block需要被拷?贝和?入队,同时适当的?工作线程需要被通知。不要将?一张图?片的每个像素作为?一个block提交?至队列,GCD的优点就半途夭折了。如果你不确定,那么请进?行试验。将程序平?行计算化是?一种优化措施,在修改代码之前你必须再三思索,确定修改是有益的(还有确保你修改了正确的地?方)。
Subsystem并发运算
前?面的章节我们讨论了在程序的单个subsystem中发挥多核⼼心的优势。下来我们要跨越多个?子系统。
例如,设想?一个程序要打开?一个包含meta信息的?文档。?文档数据本?身需要解析并转换?至模型对象来显?示,meta信息也需要解析和转换。但是,?文档数据和meta信息不需要交互。我们可以为?文档和meta各创建?一个dispatch
queue,然后并发执?行。?文档和meta的解析代码都会各?自串?行执?行,从?而不?用考虑线程安全(只要没有?文档和meta之间
共享的数据),但是它们还是并发执?行的。?一旦?文档打开了,程序需要响应?用户操作。例如,可能需要进?行拼写检查、代码?高亮、字数统计、?自动保存或者其他什么。如果每个任务都被实现为在不同的dispatch
queue中执?行,那么这些任务会并发执?行,并各?自将其他任务的运算考虑在内(respect
to each other),从?而省去了多线程编程的?麻烦。
使?用dispatch source(下次我会讲到),我们可以让GCD将事件直接传递给?用户队列。例如,程序中监视socket连接的代码可以被置于它?自?己的dispatch
queue中,这
样它会异步执?行,并且执?行时会将程序其他部分的运算考虑在内。另外,如果使?用?用户队列的话,这个模块会串?行执?行,简化程序。
我们讨论了如何使?用GCD来提升程序性能以及发挥多核系统的优势。尽管我们需要?比较谨慎地编写并发程序,GCD还是使得我们能更简单地发挥系统的可?用计算资源。下?一篇中,我们将讨论dispatch
source,也就是GCD的监视内部、外部事件的机制。
介绍(三)何为Dispatch
简单来说,dispatch source是?一个监视某些类型事件的对象。当这些事件发?生时,它?自动将?一个block放?入?一个dispatch
queue的执?行例程中。说的貌似有点不清不楚。我们到底讨论哪些事件类型?
下?面是GCD 10.6.0版本?支持的事件:
1. Mach port send right state changes.2. Mach port receive right state changes.3. External process state change.
4. File descriptor ready for read.
5. File descriptor ready for write.
6. Filesystem node event.
7. POSIX signal.
8. Custom timer.
9. Custom event.
这是?一堆很有?用的东?西,它?支持所有kqueue所?支持的事件(kqueue是什么??见http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什么??见http://en.wikipedia.org/wiki/Mach_(kernel))端?口、内建计时器?支持(这样我们就不?用使?用超时参数来创建?自?己的计时器)和?用户事件。
这些事件?里?面多数都可以从名字中看出含义,但是你可能想知道啥叫?用户事件。简单地说,这种事件是由你调?用dispatch_source_merge_data函数来向?自?己发出的信号。
这个名字对于?一个发出事件信号的函数来说,太怪异了。这个名字的来由是GCD会在事件句柄被执?行之前?自动将多个事件进?行联结。你可以将数据“拼接”?至dispatchsource中任意次,并且如果dispatch
queue在这期间繁忙的话,GCD只会调?用该句柄?一次(不要觉得这样会有问题,看完下?面的内容你就明?白了)。
?用户事件有两种:
DISPATCH_SOURCE_TYPE_DATA_ADD
DISPATCH_SOURCE_TYPE_DATA_OR.?用户事件源有个
unsigned long data属性,我们将?一
个 unsigned long传?入
dispatch_source_merge_data。当使?用
_ADD版本时,事件在联结时会把这些数字相加。当使?用
_OR版本时,事件在联结时会把这些数字逻辑与运算。当事件句柄执?行时,我们可以使?用dispatch_source_get_data函数访问当前值,然后这个值会被重置为0。
让我假设?一种情况。假设?一些异步执?行的代码会更新?一个进度条。因为主线程只不过是GCD的另?一个dispatch
queue?而已,所以我们可以将GUI更新?工作push到主线程中。然?而,这些事件可能会有?一?大堆,我们不想对GUI进?行频繁?而累赘的更新,理想的情况是当主线程繁忙时将所有的改变联结起来。
?用dispatch source就完美了,使?用DISPATCH_SOURCE_TYPE_DATA_ADD,我们可以将?工作拼接起来,然后主线程可以知道从上?一次处理完事件到现在?一共发?生了多少改变,然后将这?一整段改变?一次更新?至进度条。
啥也不说了,上代码:
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
[progressIndicator incrementBy:dispatch_source_get_data(source)];
dispatch_resume(source);
dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
(对于这段代码,我很想说点什么,我第?一次?用dispatch source时,我纠结了很久很久,真让?人蛋疼:Dispatch
source启动时默认状态是挂起的,我们创建完毕之后得主动恢复,否则事件不会被传递,也不会被执?行)
假设你已经将进度条的min/max值设置好了,那么这段代码就完美了。数据会被并发处理。当每?一段数据完成后,会通知dispatch
source并将dispatch source data加1,这样我们就认为?一个单元的?工作完成了。事件句柄根据已完成的?工作单元来更新进度条。若主线程?比较空闲并且这些?工作单元进?行的?比较慢,那么事件句柄会在每个?工作单元完成的时候被调?用,实时更新。如果主线程忙于其他?工作,或者?工作单元完成速度很快,那么完成事件会被联结起来,导致进度条只在主线程变得可?用时才被更新,并且?一次将积累的改变更新?至GUI。
现在你可能会想,听起来倒是不错,但是要是我不想让事件被联结呢?有时候你可能想让每?一次信号都会引起响应,什么后台的智能玩意?儿统统不要。啊。。其实很简单的,别把?自?己绕进去了。如果你想让每?一个信号都得到响应,那使?用dispatch_async函数不就?行了。实际上,使?用的dispatch
source?而不使?用dispatch_async的唯?一原因就是利?用联结的优势。
上?面就是怎样使?用?用户事件,那么内建事件呢?看看下?面这个例?子,?用GCD读取标准输?入:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
dispatch_source_set_event_handler(stdinSource, ^{
char buf[1024];
int len = read(STDIN_FILENO, buf, sizeof(buf));
if(len & 0)
NSLog(@&Got data from stdin: %.*s&, len, buf);
dispatch_resume(stdinSource);
STDIN_FILENO,
globalQueue);
简单的要死!因为我们使?用的是全局队列,句柄?自动在后台执?行,与程序的其他部分并?行,这意味着对这种情况的提速:事件进?入程序时,程序正在处理其他事务。
这是标准的UNIX?方式来处理事务的好处,不?用去写loop。如果使?用经典的
read调?用,我们还得万分留神,因为返回的数据可能?比请求的少,还得忍受?无厘头的“errors”,?比如
(系统调?用中断)。使?用GCD,我们啥都不?用管,就从这些蛋疼的情况?里解脱了。如果我们在?文件描述符中留下了未读取的数据,GCD会再次调?用我们的句柄。
对于标准输?入,这没什么问题,但是对于其他?文件描述符,我们必须考虑在完成读写之后怎样清除描述符。对于dispatch source还处于活跃状态时,我们决不能关闭描述符。如果另?一个?文件描述符被创建了(可能是另?一个线程创建的)并且新的描述符刚好被分配了相同的数字,那么你的dispatch
source可能会在不应该的时候突然进?入读写状态。de这个bug可不是什么好玩的事?儿。
适当的清除?方式是使?用
dispatch_source_set_cancel_handler,并传?入?一个block来关闭?文件描述符。然后我们使?用
dispatch_source_cancel来取消dispatch
source,使得句柄被调?用,然后?文件描述符被关闭。
使?用其他dispatch source类型也差不多。总的来说,你提供?一个source(mach
port、?文件描述符、进程ID等等)的区分符来作为diapatch
source的句柄。mask参数通常不会被使?用,但是对于
DISPATCH_SOURCE_TYPE_PROC
来说mask指的是我们想要接受哪?一种进程事件。然后我们提供?一个句柄,然后恢复这个source(前?面我加粗字体所说的,得先恢复),搞定。dispatch
source也提供?一个特定于source的data,我们使?用
dispatch_source_get_data函数来访问它。例如,?文件描述符会给出?大致可?用的字节数。进程source会给出上次调?用之后发?生的事件的mask。具体每种source给出的data的含义,看man
计时器事件稍有不同。它们不使?用handle/mask参数,计时器事件使?用另外?一个函数
dispatch_source_set_timer
来配置计时器。这个函数使?用三个参数来控制计时器触发:
start参数控制计时器第?一次触发的时刻。参数类型是
dispatch_time_t,这是?一个opaque类型,我们不能直接操作它。我们得需要dispatch_time
和dispatch_walltime
函数来创建它们。另外,常量
DISPATCH_TIME_NOW
DISPATCH_TIME_FOREVER
通常很有?用。
interval参数没什么好解释的。
leeway参数?比较有意思。这个参数告诉系统我们需要计时器触发的精准程度。所有的计时器都不会保证100%精准,这个参数?用来告诉系统你希望系统保证精准的努?力程度。如果你希望?一个计时器没五秒触发?一次,并且越准越好,那么你传递0为参数。另外,如果是?一个周期性任务,?比如检查email,那么你会希望每?十分钟检查?一次,但是不?用那么精准。所以你可以传?入60,告诉系统60秒的误差是可接受的。
这样有什么意义呢?简单来说,就是降低资源消耗。如果系统可以让cpu休息?足够?长的时间,并在每次醒来的时候执?行?一个任务集合,?而不是不断的醒来睡去以执?行任务,那么系统会更?高效。如果传?入?一个?比较?大的leeway给你的计时器,意味着你允许系统拖延你的计时器来将计时器任务与其他任务联合起来?一起执?行。
现在你知道怎样使?用GCD的dispatch
source功能来监视?文件描述符、计时器、联结的?用户事件以及其他类似的?行为。由于dispatch source完全与dispatch
queue相集成,所以你可以使?用任意的dispatch queue。你可以将?一个dispatch
source的句柄在主线程中执?行、在全局队列中并发执?行、或者在?用户队列中串?行执?行(执?行时会将程序的其他模块的运算考虑在内)。
下?一篇我会讨论如何对dispatch queue进?行挂起、恢复、重定??目标操作;如何使?用dispatch
semaphore;如何使?用GCD的?一次性初始化功能。介绍(四)完结
Dispatch Queue挂起
dispatch queue可以被挂起和恢复。使?用
dispatch_suspend函数来挂起,使?用
dispatch_resume
函数来恢复。这两个函数的?行为是如你所愿的。另外,这两个函
数也可以?用于dispatch source。
?一个要注意的地?方是,dispatch queue的挂起是block粒度的。换句话说,挂起?一个queue并不会将当前正在执?行的block挂起。它会允许当前执?行的block执?行完毕,然
后后续的block不再会被执?行,直?至queue被恢复。
还有?一个注意点:从man?页上得来的:如果你挂起了?一个queue或者source,那么销毁它之前,必须先对其进?行恢复。
Dispatch Queue??目标指定
所有的?用户队列都有?一个??目标队列概念。从本质上讲,?一个?用户队列实际上是不执?行任何任务的,但是它会将任务传递给它的??目标队列来执?行。通常,??目标队列是默认
优先级的全局队列。
?用户队列的??目标队列可以?用函数
dispatch_set_target_queue来修改。我们可以将任意dispatch
queue传递给这个函数,甚?至可以是另?一个?用户队列,只要别构成循环就?行。这个函数可以?用来设定?用户队列的优先级。?比如我们可以将?用户队列的??目标队列设定为低优先级的全局队列,那么我们的?用户队列中的任务都会以低优先级执?行。?高优先级也是?一样道理。
有?一个?用途,是将?用户队列的??目标定为main queue。这会导致所有提交到该?用户队列的block在主线程中执?行。这样做来替代直接在主线程中执?行代码的好处在于,我们的?用户队列可以单独地被挂起和恢复,还可以被重定??目标?至?一个全局队列,然后所有的block会变成在全局队列上执?行(只要你确保你的代码离开主线程不会有问题)。
还有?一个?用途,是将?一个?用户队列的??目标队列指定为另?一个?用户队列。这样做可以强制多个队列相互协调地串?行执?行,这样?足以构建?一组队列,通过挂起和暂停那个??目标队列,我们可以挂起和暂停整个组。想象这样?一个程序:它扫描?一组??目录并且加载??目录中的内容。为了避免磁盘竞争,我们要确定在同?一个物理磁盘上同时只有?一个?文件加载任务在执?行。?而希望可以同时从不同的物理磁盘上读取多个?文件。要实现这个,我们要做的就是创建?一个dispatch
queue结构,该结构为磁盘结构的镜像。
?首先,我们会扫描系统并找到各个磁盘,为每个磁盘创建?一个?用户队列。然后扫描?文件系统,并为每个?文件系统创建?一个?用户队列,将这些?用户队列的??目标队列指向合适的磁盘?用户队列。最后,每个??目录扫描器有?自?己的队列,其??目标队列指向??目录所在的?文件系统的队列。??目录扫描器枚举?自?己的??目录并为每个?文件向?自?己的队列提交?一个block。由于整个系统的建?立?方式,就使得每个物理磁盘被串?行访问,?而多个物理磁盘被并?行访问。除了队列初始化过程,我们根本不需要?手动干预什么东?西。
dispatch的信号量是像其他的信号量?一样的,如果你熟悉其他多线程系统中的信号量,那么这?一节的东?西再好理解不过了。信号量是?一个整形值并且具有?一个初始计数值,并且?支持两个操作:信号通知和等待。当?一个信号量被信号通知,其计数会被增加。当?一个线程在?一个信号量上等待
时,线程会被阻塞(如果有必要的话),直?至计数器?大于零,然后线程会减少这个计数。
我们使?用函数 dispatch_semaphore_create
来创建dispatch信号量,使?用函数
dispatch_semaphore_signal
来信号通知,使?用函
数dispatch_semaphore_wait
来等待。这些函数的man?页有两个很好的例?子,展?示了怎样使?用信号量来同步任务和有限资源访问控制。单次初始化
GCD还提供单次初始化?支持,这个与pthread中的函数
pthread_once
很相似。GCD提供的?方式的优点在于它使?用block?而?非函数指针,这就允许更?自然的代码?方式:这个特性的主要?用途是惰性单例初始化或者其他的线程安全数据共享。典型的单例初始化技术看起来像这样(线程安全的):
+ (id)sharedWhatever
static Whatever *whatever =
@synchronized([Whatever class])
if(!whatever)
whatever = [[Whatever alloc] init];
这挺好的,但是代价?比较昂贵;每次调?用
+sharedWhatever
函数都会付出取锁的代价,即使这个锁只需要进?行?一次。确实有更?风骚的?方式来实现这个,使?用类似双向锁或者是原?子操作的东?西,但是这样挺难弄?而且容易出错。
使?用GCD,我们可以这样重写上?面的?方法,使?用函数
dispatch_once:
+ (id)sharedWhatever
static dispatch_once_
static Whatever *whatever =
dispatch_once(&pred, ^{
whatever = [[Whatever alloc] init];
这个稍微?比
@synchronized?方法简单些,并且GCD确保以更快的?方式完成这些检测,它保证block中的代码在任何线程通过
dispatch_once
调?用之前被执?行,但它不会强制每次调?用这个函数都让代码进?行同步控制。实际上,如果你去看这个函数所在的头?文件,你会发现??目前它的实现其实是?一个宏,进?行了内联的初始化测试,这意味着通常情况下,你不?用付出函数调?用的负载代价,并且会有更少的同步控制负载。
这?一章,我们介绍了dispatch queue的挂起、恢复和??目标重定,以及这些功能的?一些?用途。另外,我们还介绍了如何使?用dispatch
信号量和单次初始化功能。到此,我已经完成了GCD如何运作以及如何使?用的介绍。
实战?一:使?用串?行队列实现简单的预加载其主要思路是使?用gcd创建串?行队列,然后在此队列中先后执?行两个任务:1.预加载?一个viewController
2.将这个viewController推?入
@implementation DWAppDelegate
dispatch_queue_t _serialQ
UINavigationController *_navC
- (dispatch_queue_t)serialQueue
if (!_serialQueue) {
_serialQueue = dispatch_queue_create(&serialQueue&, DISPATCH_QUEUE_SERIAL);//创建串?行队列
return _serialQ
- (void)prepareViewController
dispatch_async([self serialQueue], ^{//把block中的任务放?入串?行队列中执?行,这是第?一个任务self.viewController
= [[[DWViewController alloc] init] autorelease];sleep(2);//假装这个viewController创建起来很花时间。。其实view都还没加载,根本不花时间。NSLog(@&prepared&);
- (void)goToViewController
dispatch_async([self serialQueue], ^{//第?二个任务,推?入viewControllerNSLog(@&go&);
dispatch_async(dispatch_get_main_queue(), ^{//涉及UI更新的操作,放?入主线程中[_navController
pushViewController:self.viewController animated:YES];
- (void)dealloc
dispatch_release(_serialQueue);
[_navController release];
[_window release];
[_viewController release];
[super dealloc];
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
[self prepareViewController];
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
DWViewController *viewController = [[[DWViewController alloc] initWithNibName:@&DWViewController& bundle:nil] autorelease];
viewController.view.backgroundColor = [UIColor blueColor];
_navController = [[UINavigationController alloc] initWithRootViewController:viewController];
self.window.rootViewController = _navC
[self goToViewController];
[self.window makeKeyAndVisible];
return YES;}
实战:资源竞争概述
我将分四步来带?大家研究研究程序的并发计算。第?一步是基本的串?行程序,然后使?用GCD把它并?行计算化。如果你想顺着步骤来尝试这些程序的话,可以下载源码。注意,别运?行imagegcd2.m,这是个反?面教材。。
我们的程序只是简单地遍历~/Pictures然后?生成缩略图。这个程序是个命令?行程序,没有图形界?面(尽管是使?用Cocoa开发库的),主函数如下:
int main(int argc, char **argv)
NSAutoreleasePool *outerPool = [NSAutoreleasePool new];
NSApplicationLoad();
NSString *destination = @&/tmp/imagegcd&;
[[NSFileManager defaultManager] removeItemAtPath: destination error: NULL];
[[NSFileManager defaultManager] createDirectoryAtPath: destination
withIntermediateDirectories: YES
attributes: nil
error: NULL];
NSString *dir = [@&~/Pictures& stringByExpandingTildeInPath];
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath: dir];
int count = 0;
for(NSString *path in enumerator)
NSAutoreleasePool *innerPool = [NSAutoreleasePool new];
if([[[path pathExtension] lowercaseString] isEqual: @&jpg&])
path = [dir stringByAppendingPathComponent: path];
NSData *data = [NSData dataWithContentsOfFile: path];
NSData *thumbnailData = ThumbnailDataForData(data);
if(thumbnailData)
[innerPool release];
[outerPool release];
NSString *thumbnailName = [NSString stringWithFormat: @&%d.jpg&, count++];
NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];
[thumbnailData writeToFile: thumbnailPath atomically: NO];
如果你要看到所有的副主函数的话,到?文章顶部下载源代码吧。当前这个程序是imagegcd1.m。程序中重要的部分都在这?里了。.
函数只是简单的计时函数(内部实现是使?用的gettimeofday函数)。ThumbnailDataForData函数使?用Cocoa库来加载图?片数据?生成Image对象,然后将图?片缩?小到320×320?大?小,最后将其编码为JPEG格式。
简单?而天真的并发
乍?一看,我们感觉将这个程序并发计算化,很容易。循环中的每个迭代器都可以放?入GCD global queue中。我们可以使?用dispatch
queue来等待它们完成。为了保证每次迭代都会得到唯?一的?文件名数字,我们使?用OSAtomicIncrement32来原?子操作级别的增加count数:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
__block uint32_t count = -1;
for(NSString *path in enumerator)
dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{
if([[[path pathExtension] lowercaseString] isEqual: @&jpg&])
NSString *fullPath = [dir stringByAppendingPathComponent: path];
NSData *data = [NSData dataWithContentsOfFile: fullPath];
NSData *thumbnailData = ThumbnailDataForData(data);
if(thumbnailData)
NSString *thumbnailName = [NSString stringWithFormat: @&%d.jpg&,
OSAtomicIncrement32(&)];
NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];
[thumbnailData writeToFile: thumbnailPath atomically: NO];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
这个就是imagegcd2.m,但是,注意,别运?行这个程序,有很?大的问题。如果你?无视我的警告还是运?行这个imagegcd2.m了,你现在很有可能是在重启了电脑后,?又打开了我的?页?面。。如果你乖乖地没有运?行这个程序的话,运?行这个程序发
?生的情况就是(如果你有很多很多图?片在~/Pictures中):电脑没反应,好久好久都不动,假死了。。问题在哪
问题出在哪?就在于GCD的智能上。GCD将任务放到全局线程池中运?行,这个线程池的?大?小根据系统负载来随时改变。例如,我的电脑有四核,所以如果我使?用GCD加载任务,GCD会为我每个cpu核创建?一个线程,也就是四个线程。如果电脑上其他任务需要进?行的话,GCD会减少线程数来使其他任务得以占?用cpu资源来完成。
但是,GCD也可以增加活动线程数。它会在其他某个线程阻塞时增加活动线程数。假设现在有四个线程正在运?行,突然某个线程要做?一个操作,?比如,读?文件,这个线程就会等待磁盘响应,此时cpu核⼼心会处于未充分利?用的状态。这是GCD就会发现这个状态,然后创建另?一个线程来填补这个资源浪费空缺。
现在,想想上?面的程序发?生了啥?主线程?非常迅速地将任务不断放?入global queue中。GCD以?一个少量?工作线程的状态开始,然后开始执?行任务。这些任务执?行了?一些很轻量的?工作后,就开始等待磁盘资源,慢得不像话的磁盘资源。
我们别忘记磁盘资源的特性,除?非你使?用的是SSD或者?牛逼的RAID,否则磁盘资源会在竞争的时候变得异常的慢。。刚开始的四个任务很轻松地就同时访问到了磁盘资源,然后开始等待磁盘资源返回。这时GCD发现CPU开始空闲了,它继续增加?工作线程。然后,这些线程执?行更多的
磁盘读取任务,然后GCD再创建更多的?工资线程。。。
可能在某个时间?文件读取任务有完成的了。现在,线程池中可不?止有四个线程,相反,有成百上千个。。。GCD?又会尝试将?工作线程减少(太多使?用CPU资源的线程),但是减少线程是由条件的,GCD不可以将?一个正在执?行任务的线程杀掉,并且也不能将这样的任务暂停。它必须等待这个任务完成。所有这些情况都导致GCD?无法减少?工作线程数。
然后所有这上百个线程开始?一个个完成了他们的磁盘读取?工作。它们开始竞争CPU资源,当然CPU在处理竞争上?比磁盘先进多了。问题在于,这些线程读完?文件后开始编码这些图?片,如果你有很多很多图?片,那么你的内存将开始爆仓。。然后内存耗尽咋办?虚拟内存啊,虚拟内存是啥,磁盘资源啊。Oh
然后进?入了?一个恶性循环,磁盘资源竞争导致更多的线程被创建,这些线程导致更多的内存使?用,然后内存爆仓导致虚拟内存交换,直?至GCD创建了系统规定的线程数上限(可能是512个),?而这些线程?又没法被杀掉或暂停。。。
这就是使?用GCD时,要注意的。GCD能智能地根据CPU情况来调整?工作线程数,但是它却?无法监视其他类型的资源状况。如果你的任务牵涉?大量IO或者其他会导致线程block的东?西,你需要把握好这个问题。
问题的根源来?自于磁盘IO,然后导致恶性循环。解决了磁盘资源碰撞,就解决了这个问题。
GCD的custom
queue使得这个问题易于解决。Custom queue是串?行的。如果我们创建?一个custom
queue然后将所有的?文件读写任务放?入这个队列,磁盘资源的同时访
问数会?大?大降低,资源访问碰撞就避免了。
虾?米是我们修正后的代码,使?用IO queue(也就是我们创建的custom
queue专?门?用来读写磁盘):
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_queue_t ioQueue = dispatch_queue_create(&com.mikeash.imagegcd.io&, NULL);
dispatch_group_t group = dispatch_group_create();
__block uint32_t count = -1;
for(NSString *path in enumerator)
if([[[path pathExtension] lowercaseString] isEqual: @&jpg&])
NSString *fullPath = [dir stringByAppendingPathComponent: path];
dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{
NSData *data = [NSData dataWithContentsOfFile: fullPath];
dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{
NSData *thumbnailData = ThumbnailDataForData(data);
if(thumbnailData)
NSString *thumbnailName = [NSString stringWithFormat: @&%d.jpg&,
OSAtomicIncrement32(&)];
NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];
dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{
[thumbnailData writeToFile: thumbnailPath atomically: NO];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
这个就是我们的
imagegcd3.m.GCD使得我们很容易就将任务的不同部分放?入相同的队列中去(简单地嵌套?一下dispatch)。这次我们的程序将会表现地很好。。。我是说多数情况。。。。问题在于任务中的不同部分不是同步的,导致了整个程序的不稳定。我们的新程序的整个流程如下:
Main Thread
Concurrent Queue
find paths
-----------&
write &-----------
图中的箭头是?非阻塞的,并且会简单地将内存中的对象进?行缓冲。
现在假设?一个机器的磁盘?足够快,快到?比CPU处理任务(也就是图?片处理)要快。其实不难想象:虽然CPU的动作很快,但是它的?工作更繁重,解码、压缩、编码。从磁盘读取的数据开始填满IO
queue,数据会占?用内存,很可能越占越多(如果你的~/Pictures中有很多很多图?片的话)。
然后你就会内存爆仓,然后开始虚拟内存交换。。。?又来了。。
这就会像第?一次?一样导致恶性循环。?一旦任何东?西导致?工作线程阻塞,GCD就会创建更多的线程,这个线程执?行的任务?又会占?用内存(从磁盘读取的数据),然后?又开始交换内存。。
结果:这个程序要么就是运?行地很顺畅,要么就是很低效。
注意如果磁盘速度?比较慢的话,这个问题依旧会出现,因为缩略图会被缓冲在内存?里,不过这个问题导致的低效?比较不容易出现,因为缩略图占的内存少得多。
真正的修复
由于上?一次我们的尝试出现的问题在于没有同步不同部分的操作,所以让我写出同步的代码。最简单的?方法就是使?用信号量来限制同时执?行的任务数量。
那么,我们需要限制为多少呢?
显然我们需要根据CPU的核数来限制这个量,我们?又想?马?儿好?又想?马?儿不吃草,我们就设置为cpu核数的两倍吧。不过这?里只是简单地这样处理,GCD的作?用之?一就是让我们不?用关⼼心操作系统的内部信息(?比如cpu数),现在?又来读取cpu核数,确实不太妙。也许我们在实际应?用中,可以根据其他需求来定义这个限制量。
现在我们的主循环代码就是这样了:
dispatch_queue_t ioQueue = dispatch_queue_create(&com.mikeash.imagegcd.io&, NULL);
int cpuCount = [[NSProcessInfo processInfo] processorCount];
dispatch_semaphore_t jobSemaphore = dispatch_semaphore_create(cpuCount * 2);
dispatch_group_t group = dispatch_group_create();
__block uint32_t count = -1;
for(NSString *path in enumerator)
WithAutoreleasePool(^{
if([[[path pathExtension] lowercaseString] isEqual: @&jpg&])
NSString *fullPath = [dir stringByAppendingPathComponent: path];
dispatch_semaphore_wait(jobSemaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{
NSData *data = [NSData dataWithContentsOfFile: fullPath];
dispatch_group_async(group, globalQueue, BlockWithAutoreleasePool(^{
NSData *thumbnailData = ThumbnailDataForData(data);
if(thumbnailData)
NSString *thumbnailName = [NSString stringWithFormat: @&%d.jpg&,
OSAtomicIncrement32(&)];
NSString *thumbnailPath = [destination stringByAppendingPathComponent: thumbnailName];
dispatch_group_async(group, ioQueue, BlockWithAutoreleasePool(^{
[thumbnailData writeToFile: thumbnailPath atomically: NO];
dispatch_semaphore_signal(jobSemaphore);
dispatch_semaphore_signal(jobSemaphore);
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
最终我们写出了?一个能平滑运?行且?又快速处理的程序。
我测试了?一些运?行时间,对7913张图?片:程序处理时间
注意,因为我?比较懒。所以我在运?行这些测试的时候,没有关闭电脑上的其他程序。。。严格的进?行对照的话,实在是太蛋疼了。。
所以这个数值我们只是参考?一下。
?比较有意思的是,3和4的执?行状况差不多,?大概是因为我电脑有15g可?用内存吧。。。内存?比较?小的话,这个imagegcd3应该跑的很吃?力,因为我发现它使?用最多的时候,占?用了10g内存。?而4的话,没有占多少内存。
GCD是个?比较范特?西的技术,可以办到很多事?儿,但是它不能为你办所有的事?儿。所以,对于进?行IO操作并且可能会使?用?大量内存的任务,我们必须仔细斟酌。当然,即使这样,GCD还是为我们提供了简单有效的?方法来进?行并发计算。&
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:27472次
排名:千里之外
原创:67篇
(2)(1)(3)(1)(2)(9)(59)

我要回帖

更多关于 美白的方法 的文章

 

随机推荐