iOS开发 多线程(一)-转自MJ的GCD详解

1、简介网络

在iOS全部实现多线程的方案中,GCD应该是最有魅力的,由于GCD自己是苹果公司为多核的并行运算提出的解决方案。GCD在工做时会自动利用更多的处理器核心,以充分利用更强大的机器。GCD是Grand Central Dispatch的简称,它是基于C语言的。若是使用GCD,彻底由系统管理线程,咱们不须要编写线程代码。只需定义想要执行的任务,而后添加到适当的调度队列(dispatch queue)。GCD会负责建立线程和调度你的任务,系统直接提供线程管理数据结构


2、调度队列(dispath queue)多线程

1.GCD的一个重要概念是队列,它的核心理念:将长期运行的任务拆分红多个工做单元,并将这些单元添加到dispath queue中,系统会为咱们管理这些dispath queue,为咱们在多个线程上执行工做单元,咱们不须要直接启动和管理后台线程。并发

2.系统提供了许多预约义的dispath queue,包括能够保证始终在主线程上执行工做的dispath queue。也能够建立本身的dispath queue,并且能够建立任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工做单元将始终按照加入dispath queue的顺序启动。app

3.dispatch queue按先进先出的顺序,串行或并发地执行任务异步

1> serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务async

2> concurrent dispatch queue则尽量多地启动任务并发执行函数


3、建立和管理dispatch queueoop

1.得到全局并发Dispatch Queue (concurrent dispatch queue)性能

1> 并发dispatch queue能够同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在以前的任务完成以前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各类因素包括:可用核数量、其它进程正在执行的工做数量、其它串行dispatch queue中优先任务的数量等.

2> 系统给每一个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不须要显式地建立这些queue,使用dispatch_get_global_queue函数来获取这三个queue:

// 获取默认优先级的全局并发dispatch queue  
dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0便可

3> 虽然dispatch queue是引用计数的对象,但你不须要retain和release全局并发queue。由于这些queue对应用是全局的,retain和release调用会被忽略。你也不须要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue得到queue就好了。


2.建立串行Dispatch Queue (serial dispatch queue)

1> 应用的任务须要按特定顺序执行时,就须要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不同的是,串行queue确保任务按可预测的顺序执行。并且只要你异步地提交任务到串行queue,就永远不会产生死锁

2> 你必须显式地建立和管理全部你使用的串行queue,应用能够建立任意数量的串行queue,但不要为了同时执行更多任务而建立更多的串行queue。若是你须要并发地执行大量任务,应该把任务提交到全局并发queue

3> 利用dispatch_queue_create函数建立串行queue,两个参数分别是queue名和一组queue属性

dispatch_queue_t queue;  
queue = dispatch_queue_create("cn.itcast.queue", NULL);

3.运行时得到公共Queue

GCD提供了函数让应用访问几个公共dispatch queue:

1> 使用dispatch_get_current_queue函数做为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象以外调用这个函数会返回应用的默认并发queue。
2> 使用dispatch_get_main_queue函数得到应用主线程关联的串行dispatch queue
3> 使用dispatch_get_global_queue来得到共享的并发queue


4.Dispatch Queue的内存管理

1> Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型。当你建立一个串行dispatch queue时,初始引用计数为 1,你可使用dispatch_retain和dispatch_release函数来增长和减小引用计数。当引用计数到达 0 时,系统会异步地销毁这个queue

2> 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时可以保留在内存中。和OC对象同样,通用的规则是若是使用一个传递过来的queue,你应该在使用前retain,使用完以后release

3> 你不须要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue

4> 即便你实现的是自动垃圾收集的应用,也须要retain和release建立的dispatch queue和其它dispatch对象。GCD 不支持垃圾收集模型来回收内存


4、添加任务到queue

要执行一个任务,你须要将它添加到一个适当的dispatch queue,你能够单个或按组来添加,也能够同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。通常能够用一个block来封装任务内容。

1.添加单个任务到queue

1> 异步添加任务

你能够异步或同步地添加一个任务到Queue,尽量地使用dispatch_async或dispatch_async_f函数异步地调度任务。由于添加任务到Queue中时,没法肯定这些代码何时可以执行。所以异步地添加block或函数,可让你当即调度这些代码的执行,而后调用线程能够继续去作其它事情。特别是应用主线程必定要异步地 dispatch 任务,这样才能及时地响应用户事件

2> 同步添加任务

少数时候你可能但愿同步地调度任务,以免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,由于这样作确定会致使死锁;而并发queue也应该避免这样作。

3> 代码演示

// 调用前,查看下当前线程  
NSLog(@"当前调用线程:%@", [NSThread currentThread]);  
  
// 建立一个串行queue  
dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);  
  
dispatch_async(queue, ^{  
    NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);  
});  
  
dispatch_sync(queue, ^{  
    NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);  
});  
// 销毁队列  
dispatch_release(queue);
    打印信息:



  1. 2013-02-03 09:03:37.348 thread[6491:c07] 当前调用线程:<NSThread: 0x714fa80>{name = (null), num = 1}  

  2. 2013-02-03 09:03:37.349 thread[6491:1e03] 开启了一个异步任务,当前线程:<NSThread: 0x74520a0>{name = (null), num = 3}  

  3. 2013-02-03 09:03:37.350 thread[6491:c07] 开启了一个同步任务,当前线程:<NSThread: 0x714fa80>{name = (null), num = 1}  


2.并发地执行循环迭代

若是你使用循环执行固定次数的迭代, 并发dispatch queue可能会提升性能。

例以下面的for循环:

int i;  
int count = 10;  
for (i = 0; i < count; i++) {  
   printf("%d  ",i);  
}

1> 若是每次迭代执行的任务与其它迭代独立无关,并且循环迭代执行顺序也可有可无的话,你能够调用dispatch_apply或dispatch_apply_f函数来替换循环。这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。用dispatch_apply或dispatch_apply_f时你能够指定串行或并发 queue。并发queue容许同时执行多个循环迭代,而串行queue就没太大必要使用了。

下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1


// 得到全局并发queue  
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
size_t count = 10;  
dispatch_apply(count, queue, ^(size_t i) {  
    printf("%zd ", i);  
});  
// 销毁队列  
dispatch_release(queue);


打印信息:

  1. 1 2 0 3 4 5 6 7 8 9   

能够看出,这些迭代是并发执行的

和普通for循环同样,dispatch_apply和dispatch_apply_f函数也是在全部迭代完成以后才会返回,所以这两个函数会阻塞当前线程,主线程中调用这两个函数必须当心,可能会阻止事件处理循环并没有法响应用户事件。因此若是循环代码须要必定的时间执行,能够考虑在另外一个线程中调用这两个函数。若是你传递的参数是串行queue,并且正是执行当前代码的queue,就会产生死锁


3.在主线程中执行任务

1> GCD提供一个特殊的dispatch queue,能够在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动建立这个queue,而且最后会自动销毁。非Cocoa应用若是不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,不然虽然你能够添加任务到queue,但任务永远不会被执行。

2> 调用dispatch_get_main_queue函数得到应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行

3> 代码实现,好比异步下载图片后,回到主线程显示图片

// 异步下载图片  
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];  
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];  
      
    // 回到主线程显示图片  
    dispatch_async(dispatch_get_main_queue(), ^{  
        self.imageView.image = image;  
    });  
});


4.任务中使用Objective-C对象

GCD支持Cocoa内存管理机制,所以能够在提交到queue的block中自由地使用Objective-C对象。每一个dispatch queue维护本身的autorelease pool确保释放autorelease对象,可是queue不保证这些对象实际释放的时间。若是应用消耗大量内存,而且建立大量autorelease对象,你须要建立本身的autorelease pool,用来及时地释放再也不使用的对象。


5、暂停和继续queue

咱们可使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增长queue的引用计数,调用dispatch_resume则减小queue的引用计数。当引用计数大于0时,queue就保持挂起状态。所以你必须对应地调用suspend和resume函数。挂起和继续是异步的,并且只在执行block之间(好比在执行一个新的block以前或以后)生效。挂起一个queue不会致使正在执行的block中止。


6、Dispatch Group的使用

假设有这样一个需求:从网络上下载两张不一样的图片,而后显示到不一样的UIImageView上去,通常能够这样实现

/

/ 根据url获取UIImage  
- (UIImage *)imageWithURLString:(NSString *)urlString {  
    NSURL *url = [NSURL URLWithString:urlString];  
    NSData *data = [NSData dataWithContentsOfURL:url];  
    return [UIImage imageWithData:data];  
}  
  
- (void)downloadImages {  
    // 异步下载图片  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        // 下载第一张图片  
        NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
        UIImage *image1 = [self imageWithURLString:url1];  
          
        // 下载第二张图片  
        NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
        UIImage *image2 = [self imageWithURLString:url2];  
          
        // 回到主线程显示图片  
        dispatch_async(dispatch_get_main_queue(), ^{  
            self.imageView1.image = image1;  
              
            self.imageView2.image = image2;  
        });  
    });  
}


虽然这种方案能够解决问题,但其实两张图片的下载过程并不须要按顺序执行,并发执行它们能够提升执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group可以在这种状况下帮咱们提高性能。下面先看看Dispatch Group的用处:

咱们可使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。并且Dispatch Group能够用来阻塞一个线程, 直到group关联的全部的任务完成执行。有时候你必须等待任务完成的结果,而后才能继续后面的处理。

下面用Dispatch Group优化上面的代码:


// 根据url获取UIImage  
- (UIImage *)imageWithURLString:(NSString *)urlString {  
    NSURL *url = [NSURL URLWithString:urlString];  
    NSData *data = [NSData dataWithContentsOfURL:url];  
    // 这里并无自动释放UIImage对象  
    return [[UIImage alloc] initWithData:data];  
}  
  
- (void)downloadImages {  
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
      
    // 异步下载图片  
    dispatch_async(queue, ^{  
        // 建立一个组  
        dispatch_group_t group = dispatch_group_create();  
          
        __block UIImage *image1 = nil;  
        __block UIImage *image2 = nil;  
          
        // 关联一个任务到group  
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
            // 下载第一张图片  
            NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
            image1 = [self imageWithURLString:url1];  
        });  
          
        // 关联一个任务到group  
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
            // 下载第一张图片  
            NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
            image2 = [self imageWithURLString:url2];  
        });  
          
        // 等待组中的任务执行完毕,回到主线程执行block回调  
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
            self.imageView1.image = image1;  
            self.imageView2.image = image2;  
              
            // 千万不要在异步线程中自动释放UIImage,由于当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁  
              
            // 在这里释放图片资源  
            [image1 release];  
            [image2 release];  
        });  
          
        // 释放group  
        dispatch_release(group);  
    });  
}

dispatch_group_notify函数用来指定一个额外的block,该block将在group中全部任务完成后执行


--------------------------------------

GCD中有三种队列类型:

  1. The main queue: 与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue能够调用dispatch_get_main_queue()来得到。由于main queue是与主线程相关的,因此这是一个串行队列。

  2. Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:高、中(默认)、低三个优先级队列。能够调用dispatch_get_global_queue函数传入优先级来访问队列。

  3. 用户队列: 用户队列 (GCD并不这样称呼这种队列, 可是没有一个特定的名字来形容这种队列,因此咱们称其为用户队列) 是用函数 dispatch_queue_create 建立的队列. 这些队列是串行的。正由于如此,它们能够用来完成同步机制, 有点像传统线程中的mutex。

相关文章
相关标签/搜索