IOS 之 GCD线程 心得总结

来源:互联网 发布:整容软件app 编辑:程序博客网 时间:2024/06/05 20:06

一、什么是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提供很多超越传统多线程编程的优势:

  1. 易用: GCD比之thread跟简单易用。由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束监视文件描述符周期执行代码以及工作挂起等任务。基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文。
  2. 效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
  3. 性能: GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。

三、GCD语音

1、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”来获取这些功能的更多信息。

2、Dispatch Queues

GCD的基本概念就是dispatch queue。dispatch queue是一个对象,它可以接受任务,并将任务以先到先执行的顺序来执行。dispatch queue可以是并发的或串行的。并发任务会像NSOperationQueue那样基于系统负载来合适地并发进行,串行队列同一时间只执行单一任务。

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。
      四、GCD语句

1、Dispatch Queues的生成可以有这几种方式:  




dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。  
dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。  
(注意:需要注意的是,第一个参数是自定义的queue(默认优先级就是global queue的default),而不是系统的queue(global/main)。因为你不能给系统的queue设置权限。通过上面设置,serialQueue 就有了与globalQueue一样的优先级。其实这个函数不仅可以设置queue的优先级,还可以设置queue之间的层级结构。.官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。  )

通常,我们可以在global_queue中做一些long-running的任务,完成后在main_queue中更新UI,避免UI阻塞,无法响应用户操作:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
// long-running task  
dispatch_async(dispatch_get_main_queue(), ^{  
 // update UI  
 });  
   }); 

vi

对上面自定义生成的线程队列,我们不能设置他的优先级,那要怎么设置呢?
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);  //它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);  //那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:  
dispatch_suspend(dispatchA);  //则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。 

另一个作用:

dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);   // 当不声明队列类型时,可以设置优先级

     dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

     dispatch_set_target_queue(serialQueue, globalQueue);// 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。


综上所述,可以得出结论,自定义的线程队列,其实不能修改优先级,但是可以把队列的内容放到其他线程队列中(一般是系统的队列)来改变优先级。因此队列其实就是缓存的作用,不代表什么时候会被执行

2、Dispatch 执行:  
dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。  
接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。  
dispatch_async(queue, ^{  
  //block具体代码 }); //异步执行block,函数立即返回  
dispatch_sync(queue, ^{  
  //block具体代码  }); //同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。  
实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。  
如果queue1是一个串行队列的话,这段代码立即产生死锁:  
   dispatch_sync(queue1, ^{  dispatch_sync(queue1, ^{    ......    });   ......  });   
那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  //子线程中开始网络请求数据  
  
  //更新数据模型  
  dispatch_sync(dispatch_get_main_queue(), ^{  
   //在主线程中更新UI代码    });  
});  
dispatch队列是线程安全的,可以利用串行队列实现锁的功能。
比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:   
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);  
- (void)writeDB:(NSData *)data  {  
dispatch_async(queue1, ^{  
 //write database  });  
}   
下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。  

暂停和恢复
dispatch_suspend(queue)可以暂停队列中任务的执行
dispatch_resume(queue)可以继续执行被暂停的队列。
dispatch_suspend会挂起dispatch queue,但并不意味着当前正在执行的任务会停下来,这只会导致不再继续执行还未执行的任务。dispatch_resume会唤醒已挂起的dispatch queue。你必须确保它们成对调用。(如果你挂起了一个queue或者source,那么销毁它之前,必须先对其进行恢复。)

3、Dispatch Group 线程组方法:
 线程组,是对队列的一种汇总,它能监控到其他的队列状况,并进行相应的操作

    dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

    dispatch_group_t group =dispatch_group_create();

    dispatch_group_async(group, queue, ^{

        [NSThreadsleepForTimeInterval:6];

        NSLog(@"group1 [NSThread sleepForTimeInterval:6];");

    });

    dispatch_group_async(group, queue, ^{

        [NSThreadsleepForTimeInterval:3];

        NSLog(@"group2 [NSThread sleepForTimeInterval:3];");

    });

    dispatch_group_async(group, queue, ^{

        [NSThreadsleepForTimeInterval:1];

        NSLog(@"group3 [NSThread sleepForTimeInterval:1];");

    });

    dispatch_group_notify(group,dispatch_get_main_queue(), ^{

        NSLog(@"等待全面执行完才执行");

    });



3、Dispatch 其他方法:  
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。  

// 可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);  
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数 

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{  
[NSThread sleepForTimeInterval:3];  
NSLog(@"dispatch_async1");  
});  
dispatch_async(queue, ^{  
[NSThread sleepForTimeInterval:1];  
NSLog(@"dispatch_async2");  
});  
dispatch_barrier_async(queue, ^{  // 等同串行queue(DISPATCH_QUEUE_SERIAL
NSLog(@"等待 前面两个执行完才执行");  
[NSThread sleepForTimeInterval:0.5];  
});  
dispatch_async(queue, ^{  
[NSThread sleepForTimeInterval:1];  
NSLog(@"等待dispatch_barrier_async 执行完才执行");  
});  


 //延迟执行block
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);  
double delayInSeconds = 2.0; 
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){   // code to be executed on the main queue after delay  }); 

dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次

staticdispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        // code to be executed once });


本文参考自: http://blog.csdn.net/crycheng/article/details/22214617、http://blog.csdn.net/wildfireli/article/details/18668897,并对改文进行了补充说明。


0 0
原创粉丝点击