GCD线程队列 MJ(转)

一、简介

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

二、调度队列(dispath queue)

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

2.系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue。也可以创建自己的dispath queue,而且可以创建任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。

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

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

2> concurrent dispatch queue则尽可能多地启动任务并发执行

三、创建和管理dispatch queue

1.获得全局并发Dispatch Queue (concurrent dispatch queue)

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

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

[java] view plaincopy

  1. // 获取默认优先级的全局并发dispatch queue
  2. 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属性

[java] view plaincopy

  1. dispatch_queue_t queue;
  2. 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 不支持垃圾收集模型来回收内存

四、添加任务到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> 代码演示

[java] view plaincopy

  1. // 调用前,查看下当前线程
  2. NSLog(@"当前调用线程:%@", [NSThread currentThread]);
  3. // 创建一个串行queue
  4. dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);
  5. dispatch_async(queue, ^{
  6. NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);
  7. });
  8. dispatch_sync(queue, ^{
  9. NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);
  10. });
  11. // 销毁队列
  12. dispatch_release(queue);

打印信息:

[java] view plaincopy

  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循环:

[java] view plaincopy

  1. int i;
  2. int count = 10;
  3. for (i = 0; i < count; i++) {
  4. printf("%d  ",i);
  5. }

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

[java] view plaincopy

  1. // 获得全局并发queue
  2. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  3. size_t count = 10;
  4. dispatch_apply(count, queue, ^(size_t i) {
  5. printf("%zd ", i);
  6. });
  7. // 销毁队列
  8. dispatch_release(queue);

打印信息:

[java] view plaincopy

  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> 代码实现,比如异步下载图片后,回到主线程显示图片

[java] view plaincopy

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

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

GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。

五、暂停和继续queue

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

六、Dispatch Group的使用

假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现

[java] view plaincopy

  1. // 根据url获取UIImage
  2. - (UIImage *)imageWithURLString:(NSString *)urlString {
  3. NSURL *url = [NSURL URLWithString:urlString];
  4. NSData *data = [NSData dataWithContentsOfURL:url];
  5. return [UIImage imageWithData:data];
  6. }
  7. - (void)downloadImages {
  8. // 异步下载图片
  9. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  10. // 下载第一张图片
  11. NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
  12. UIImage *image1 = [self imageWithURLString:url1];
  13. // 下载第二张图片
  14. NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
  15. UIImage *image2 = [self imageWithURLString:url2];
  16. // 回到主线程显示图片
  17. dispatch_async(dispatch_get_main_queue(), ^{
  18. self.imageView1.image = image1;
  19. self.imageView2.image = image2;
  20. });
  21. });
  22. }

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

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

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

[java] view plaincopy

  1. // 根据url获取UIImage
  2. - (UIImage *)imageWithURLString:(NSString *)urlString {
  3. NSURL *url = [NSURL URLWithString:urlString];
  4. NSData *data = [NSData dataWithContentsOfURL:url];
  5. // 这里并没有自动释放UIImage对象
  6. return [[UIImage alloc] initWithData:data];
  7. }
  8. - (void)downloadImages {
  9. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  10. // 异步下载图片
  11. dispatch_async(queue, ^{
  12. // 创建一个组
  13. dispatch_group_t group = dispatch_group_create();
  14. __block UIImage *image1 = nil;
  15. __block UIImage *image2 = nil;
  16. // 关联一个任务到group
  17. dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  18. // 下载第一张图片
  19. NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
  20. image1 = [self imageWithURLString:url1];
  21. });
  22. // 关联一个任务到group
  23. dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  24. // 下载第一张图片
  25. NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
  26. image2 = [self imageWithURLString:url2];
  27. });
  28. // 等待组中的任务执行完毕,回到主线程执行block回调
  29. dispatch_group_notify(group, dispatch_get_main_queue(), ^{
  30. self.imageView1.image = image1;
  31. self.imageView2.image = image2;
  32. // 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁
  33. // 在这里释放图片资源
  34. [image1 release];
  35. [image2 release];
  36. });
  37. // 释放group
  38. dispatch_release(group);
  39. });
  40. }

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

时间: 2024-10-28 12:39:23

GCD线程队列 MJ(转)的相关文章

GCD的队列和任务

GCD的任务  1> disptach_sync       没有创建线程的欲望,就在当前线程执行    最主要的目的,阻塞并行队列任务的执行,只有当前的同步任务执行完毕后,后续的任务才能够执行    应用场景:用户登录!  2> dispatch_async      有创建线程的欲望,但是创建多少条线程,取决与队列的类型  GCD的队列  1> 串行队列              类似于跑步,只有一条跑道,最多能够有两条    如果存在异步任务,就会在新线程中执行异步任务,而同步任务

GCD 线程处理

GCD一.GCD概述二.GCD的常规使用方法三.GCD大杀器----异步下载图片并刷新UI.四.GCD+Block五一次性执行:六延迟执行七  dispatch_time_t与秒的对应关系一.GCD概述Grand宏大的 Central中央 Dispatch调度是处理多线程的,类似于NSThread,但是比NSThread强大很多架构核心是队列和执行方法,dispatch_queue_t和dispatch_async(或dispatch_sync,同步方法一般都不使用)二.GCD的常规使用方法GC

GCD线程方式

GCD的线程方式是以C语言为基础的多线程技术,总体上分为串行队列和并行队列两种实现方式. GCD串行队列: #param mark -TWThread.gcd.seral是新创建的队列的名字,主要作用体现在断点调试阶段,可以根据名字判断线程的队列//DISPATCH_QUEUE_SERIAL,标志着这个队列是串行队列,DISPATCH_QUEUE_CONCURRENT,表示为并行队列-(void)gcdSeral { //创建一个串行队列 dispatch_queue_t q = dispatc

iOS 多线程 NSThread NSOperation NSOperationQueue GCD 线程锁 线程阻塞

iPhone中的线程应用并不是无节制的,官方给出的资料显示,iPhone OS下的主线程的堆栈大小是1M,第二个线程开始就是512KB,并且该值不能通过编译器开关或线程API函数来更改,只有主线程有直接修改UI的能力,所以一些数据层面可以开辟线程来操作进行,iOS线程的操作方法有NSThread NSOperation NSOperationQueue GCD: NSThread方法有 //NSThread自动 - (IBAction)didClickNSThreadAutoButtonActi

GCD线程

1.Serial Dispatch Queue 串行队列,每次只能执行一个线程,线程采用先进先出的执行顺序. 各个队列之间并行处理,也即每个队列对应一个系统线程,所以要注意不能过多生成Serial Dispatch Queue. 2.Concurrent Dispatch Queue 并行队列,由XNU内核决定使用有效管理的线程数,并行执行队列中的线程. 即,一个Concurrent Dispatch Queue具体包含多少个系统线程由内核XNU决定,队列中的子线程被分配在多个系统线程上并行执行

11.python并发入门(part7 线程队列)

一.为什么要用队列? 队列是一种数据结构,数据结构是一种存放数据的容器,和列表,元祖,字典一样,这些都属于数据结构. 队列可以做的事情,列表都可以做,但是为什么我们还要去使用队列呢? 这是因为在多线程的情况下,列表是一种不安全的数据结构. 为什么不安全?可以看下面这个例子: #开启两个线程,这两个线程并发从列表中移除一个元素. import threading import time l1 = [1,2,3,4,5] def pri(): while l1: a = l1[-1] print a

GCD线程组

一.继续前前导入GCD.h - (void)GCDGroup { // 创建线程组 GCDGroup *group = [GCDGroupnew]; //让线程在group 中执行(线程1) [[[GCDQueuealloc] initConcurrent] execute:^{ sleep(1); NSLog(@"1"); }inGroup:group]; //让线程在group 中执行(线程2) [[[GCDQueuealloc] initConcurrent] execute:^

线程队列

在web应用中,单个进程或者机器的响应速度有限,类似大量数据导入导出的操作的数量如果不加限制,会导致服务器cpu被吃满,导致其他一些很简单的请求无法及时响应的问题.针对这个限制提出了如下要求.1. 先到达的请求先执行: 先入先出原则2. 只能同时执行若干请求:避免cpu被吃满3. 异步执行:如果长时间执行会长期占用iis的工作线程 基于上述的要求我设计了一个队列.这个队列我们需要稍微提一个组件,ParallelExtensionsExtras 这是微软提供的一个线程的扩展,具体的自行搜索下相关资

python全栈开发 * 线程队列 线程池 协程 * 180731

一.线程队列 队列:1.Queue 先进先出 自带锁 数据安全 from queue import Queue from multiprocessing import Queue (IPC队列)2.LifoQueue后进先出 后进先出 自带锁 数据安全 from queue import LifoQueue lq=LifoQueue(5) lq.put(123) lq.put(666) lq.put(888) lq.put(999) lq.put("love") print(lq.pu