多线程 (NSThread NSOperation GCD)

多线程整理: 

        多线程概述: 在iOS中每个应用程序都有一个准们用来更新现实UI界面,处理用户触摸事件的主线程。 因此不能将太耗时的操作放在主线程中执行。不然会造成主线程堵塞(出现卡机现象), 带来极坏的用户体验。 一般的解决方案就是讲哪些耗时的操作放到另外的一个线程中执行, 多线程编程时防止主线程堵塞, 增加效率的最佳方法。 iOS 支持多个层次的多线程编程。 层次越高抽象度也越高。 使用起来也越方便, 也是苹果推荐常用的方法。 

        根据抽象层次依次列出iOS所支持的多线程编程方法。 

1、 NSThread:三种方法中最轻量级的, 但是需要管理线程的生命周期、同步、和加锁问题, 这也会导致一定的性能开销。

2、 Cocoa Operation : 是基于OC的实现。NSOperation以面向对象的思想封装了需要执行的操作, 不必关心线程的管理、同步等问题。NSOperation 是一个抽象的基类,iOS提供了两种默认的实现: NSInvocationOperation 和 NSBlockOperation 当然也可以自定义NSOperation

3、 Grand Center Dispatch (GCD,iOS4 之后支持): 提供了一些新特性、 运行库来支持多核并行编程。 它的关注点更高: 如何在多个CPU上提升效率

下面时对三种多线程编程方法的简单总结:

一、 NSThread 是一个轻量级的多线程实现方法。

       三种创建和管理线程的方法:

       1)方法一( iOS 5 之后): 先创建后启动

         NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:nil];

      2)方法二 : 创建后自动启动

         NSThread *thread = [NSThread detachNewThreadSelector:@selector(download:) withObject:nil];

      3)方法三: 隐式创建

         [self performSelectorInBackground:@selector(download:) withObject:nil];

     NSThread的常用方法:

      1>获取当前线程

      + (NSThread *)currentThread;

      2>获得主线程

      + (NSThread *)mainThread;

       3>睡眠(暂停)线程

       + (void)sleepUntilDate:(NSDate *)date;

       + (void)sleepTiemInterval:(NSTimeInterval)time;

       4>设置线程的名字

       - (void)setName:(NSString *)name;

       - (NSString *)name;

        NSThread 中线程加锁-- 线程同步

        为了防止多个线程抢夺同一个资源造成的数据安全问题 , 相应的解决办法就是对线程加锁----  @synchronized(self){}(括号中添加需要加锁的操作)

         * OC在定义属性时有nonatomicatomic两种选择 -- atomic(默认):意为原子属性,setter方法加锁 ; nonatomic:非原子属性,不会为setter方法加锁

         * atomic:线程安全,会消耗大量的资源 ; nonatomic:非线程安全,适合内存小的移动设备

        使用NSThread的注意事点:

        * 不要同时开启太多的线程, 一般1~3条线程即可, 不要超过5条

二 、NSOperation 封装了执行的相关操作, 和执行所需的数据。 并且能够以并发或非并发的方式执行这个操作。

         NSOperation本身是一个抽象类, 因此必须使用其子类,使用NSOperation子类的方式有两种:NSInvocationOperation 和 NSBlockOperation , 通常情况下NSOperation子类与NSOperationQueue结合使用, 实现对线程队列的管理

        1、单独使用NSOperation的子类管理单个子线程

 //1.创建对象
   //1>  创建NSInvocationOperation对象
   NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc] initWithTarget:  selector:  object: ] autorelease];
    //2> 创建NSBlockOperation对象
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock: ^{ //执行的内容 } ];
 
   // 2.执行操作
   //NSOperation 调用start方法即可开始执行操作, NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行
    //3.取消操作
    //operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作
    [operation cancel];
        2、  监听操作的执行:
如果我们想要在NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想要做的事情。 

- [operation setCompletionBlock:^() {
-     NSLog(@"执行完毕");
- }];

       3 使用操作队列来管理操作对象的执行。 

        NSOperationQueue是一个线程队列, 它用来调节一组NSOperation对象, 其中NSOperation可以在调节它在队列中调中的优先级

//1 创建操作队列, 通过操作队列管理操作对象
     // 1.创建主线程队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
     // 2. 创建自己的操作队列,在执行相关操作时, 会根据需求开辟子线程去执行。 
[queue setMaxConcurrentOperationCount:2]
      // 3. 设置操作在队列中的优先级
[blockOperation setQueuePriority:NSOperationQueuePriorityVeryHight];
      // 4. 添加操作对象
[queue addOperation:blockOperation]


  4、自定义NSOperation

1.简介

如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 对于并发NSOperation, 你必须重写NSOperation的多个基本方法进行实现(这里暂时先介绍非并发的NSOperation


2.非并发的NSOperation

比如叫做DownloadOperation,用来下载图片

1> 继承NSOperation,重写main方法,执行主任务

DownloadOperation.h

[java]  view plain copy
  1. #import <Foundation/Foundation.h>  
  2. @protocol DownloadOperationDelegate;  
  3.   
  4. @interface DownloadOperation : NSOperation  
  5. // 图片的url路径  
  6. @property (nonatomic, copy) NSString *imageUrl;  
  7. // 代理  
  8. @property (nonatomic, retain) id<DownloadOperationDelegate> delegate;  
  9.   
  10. - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;  
  11. @end  
  12.   
  13. // 图片下载的协议  
  14. @protocol DownloadOperationDelegate <NSObject>  
  15. - (void)downloadFinishWithImage:(UIImage *)image;  
  16. @end  
DownloadOperation.m

[java]  view plain copy
  1. #import "DownloadOperation.h"  
  2.   
  3. @implementation DownloadOperation  
  4. @synthesize delegate = _delegate;  
  5. @synthesize imageUrl = _imageUrl;  
  6.   
  7. // 初始化  
  8. - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {  
  9.     if (self = [super init]) {  
  10.         self.imageUrl = url;  
  11.         self.delegate = delegate;  
  12.     }  
  13.     return self;  
  14. }  
  15. // 释放内存  
  16. - (void)dealloc {  
  17.     [super dealloc];  
  18.     [_delegate release];  
  19.     [_imageUrl release];  
  20. }  
  21.   
  22. // 执行主任务  
  23. - (void)main {  
  24.     // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池  
  25.     @autoreleasepool {  
  26.         // ....  
  27.     }  
  28. }  
  29. @end  

2> 正确响应取消事件

operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行

NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失

以下地方可能需要调用isCancelled:
* 在执行任何实际的工作之前
* 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
* 代码中相对比较容易中止操作的任何地方

DownloadOperation的main方法实现如下

[java]  view plain copy
  1. - (void)main {  
  2.     // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池  
  3.     @autoreleasepool {  
  4.         if (self.isCancelled) return;  
  5.           
  6.         // 获取图片数据  
  7.         NSURL *url = [NSURL URLWithString:self.imageUrl];  
  8.         NSData *imageData = [NSData dataWithContentsOfURL:url];  
  9.           
  10.         if (self.isCancelled) {  
  11.             url = nil;  
  12.             imageData = nil;  
  13.             return;  
  14.         }  
  15.           
  16.         // 初始化图片  
  17.         UIImage *image = [UIImage imageWithData:imageData];  
  18.           
  19.         if (self.isCancelled) {  
  20.             image = nil;  
  21.             return;  
  22.         }  
  23.           
  24.         if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {  
  25.             // 把图片数据传回到主线程  
  26.             [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];  
  27.         }  
  28.     }  
  29. }  


三 GCD (Grand Center Dispatch)让程序平行排队执行任务, 根据可用的处理资源, 安排他们在任何可用的处理器核心上执行任务。 (GCD的'艺术'归结为选择合适的队列调度函数以提交你的工作), GCD的底层依然是用线程实现, 不过这样的可以程序员不用在关注实现的细节。 

    GCD中的FIFO队列称为dispatch queue , 他可以保证任务执行遵循FIFO规则。 GCD中至少有5个队列可以使用。主队列, 四个全局调度队列, 在加上自己创建的任何队列

队列的种类

  • 串行队列

    • dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

      • label:通常为0
      • attr:队列类型,DISPATCH_QUEUE_SERIAL
  • 并发队列

    • dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

      • label:通常为0
      • attr:队列类型 DISPATCH_QUEUE_CONCURRENT
  • 主队列(串行,只能在主线程中运行)

    • dispatch_queue_t dispatch_get_main_queue(void),获取主队列
  • 全局队列(并发)

    • dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags)


  1、主队类 mainQueue

     

//1. 使用主线程队列
//1> 获取主线程队列,  mainQueue会让在主线程中执行。 
dispatch_queu_t mainQueue = dispatch_get_main_queue()
//2> 为主线程队列提交一个任务, 以block形成提交(异步函数)
dispatch_asynac(mainQueue, ^{
NSLog(@"第一个任务");
}
//串行 vs. 并发 <!--?xml version="1.0" encoding="UTF-8" standalone="no"?--><span style="font-family:'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif;font-size:14px;">这些术语描述当任务相对于其它任务被执行,</span><span style="background-color: rgb(255, 250, 165);-evernote-highlight:true;font-family:'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif;font-size:14px;">任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行。</span>
//2 根据自己的需求创建串行队列
  //1> dispatch_queue_t mySeruakQueue = dispatch_queue_creat("myDispatch",DISPatch_QUEUE_SERIAL);
 /**
参数说明: 第一个参数是给当前创建的队列起名, 苹果官方推荐使用反域名命名, 第二个参数是制定当前的队列类型, 分为 serial 和 concurrent 两种
*/
//2> 自定义串行队列在分发任务是, 开发子线程执行.
//同步 vs. 异步:   <!--?xml version="1.0" encoding="UTF-8" standalone="no"?--><span style="font-family:'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif;font-size:14px;">在 GCD 中,这些术语描述当一个函数相对于另一个任务完成,此任务是该函数要求 GCD 执行的。一个同步函数只在完成了它预定的任务后才返回。<!--?xml version="1.0" encoding="UTF-8" standalone="no"?--><span style="font-family:'Helvetica Neue', Helvetica, STheiti, 微软雅黑, 黑体, Arial, Tahoma, sans-serif, serif;font-size:14px;">一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。</span></span>  
// 异步函数
dispathc_async(mySeruakQueue, ^{
//执行的事件
});
// 同步函数
dispatch_sync(mySeruakQueue, ^{
//执行的事件
});

  2、并发队列 concurrent Queue

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);//第一个参数是用来控制队列的优先级的, 第二个参数是苹果预留的参数, 未来才会使用, 目前设置为0;
dispatch_async(globalQueue, ^{
       NSLog(@"frist mission: %@",[NSThread isMainThread] ? @"is main thread" : @"isn't main thread");
    });
    dispatch_async(globalQueue, ^{
        NSLog(@"second mission: %@",[NSThread isMainThread] ? @"is main thread" : @"isn't main thread");
    });

2. 自己创建并发队列

  //1> 创建任务
   dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.lanou3g.GCD.myconcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
  //2> 提交任务
dispatch_async(myConcurrentQueue, ^{
        NSLog(@"frist mission: %@",[NSThread isMainThread] ? @"is main thread" : @"isn't main thread");
    });
    dispatch_async(myConcurrentQueue, ^{
        NSLog(@"second mission: %@",[NSThread isMainThread] ? @"is main thread" : @"isn't main thread");
    });

3 任务组

//创建任务组
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, myConcurrentQueue, ^{
        NSLog(@"任务一");
    });

    dispatch_group_async(group, myConcurrentQueue, ^{
        NSLog(@"任务二");
    });

   dispatch_group_notify(group, myConcurrentQueue, ^{
       NSLog(@"任务三");
   });//任务三会等待group中得 任务一和任务二 执行完后在开始执行;


4. 高级函数

</pre><p><pre name="code" class="objc">1. 延时执行任务可以在mainQueue中正常执行, 在自定义的队列中执行就会出现问题.
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延时三秒后执行的任务");
    });
}
    2. barier_async 会等待它之前被提交的任务并发执行完后,才开始执行, 并且执行完后才执行后来被提交的任务, 像一道墙, 隔开了之前和之后提交的任务. 这样的操作既可以保证读写的安全性,也可以提高读写效率.
 dispatch_barrier_async(myConcurrentQueue, ^{
        NSLog(@"写入数据任务");
        [self calculate];
    });
    3. 苹果推荐使用dispatch_once来定义单例方法

    /*
    +(id)sharedInstance
    {
        static FooClass *object = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            object = [[FoolClass alloc] init];
        });
        return object;
    }
    */
快速迭代

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t))
/**
	iterations:迭代执行的次数
	queue:任务队列
	block:迭代执行的代码
	size_t:用来定义当前迭代到第几次,需要自己添加,如在size_t后添加index索引,记录当前的迭代次数
*/

GCD定时器

实现原理

创建一个DISPATCH_SOURCE_TYPE_TIMER类型的dispatch source,并添加到dispatch queue,通过dispatch source来响应事件
通过函数void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway),来设置dispatch source的执行事件
实现代码

//获得队列
dispatch_queue_t queue = dispatch_get_main_queue();
//创建一个定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//设置定时器的各种属性(起止时间)
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
//设置
dispatch_source_set_timer(self.timer, start, interval, 0);

//设置回调
dispatch_source_set_event_handler(self.timer, ^{
    //定时器被触发时所要执行的代码
});

//开启定时器
dispatch_resume(self.timer);
//取消定时器
dispatch_cancel(self.timer);



    •  

      5 线程间通信

      • 从主线程到子线程

        • 注意

          • 只有异步函数与并发队列的组合,才会开启新的线程,使任务并发执行
          • 通常使用异步函数将任务添加到并发队列中,来实现从主线程到子线程的通信
        • 实现代码

          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //需要在子线程中执行的任务代码 })
      • 从子线程到主线程

        • 注意

          • 主队列中的任务只能在主线程中执行
          • 通常使用异步/同步函数将任务添加到主队列中,来实现从子线程到主线程的通信
        • 实现代码

          dispatch_async(dispatch_get_main_queue(), ^{
                  //需要在主线程中执行的代码
              })


      总结:
      为了保证数据访问时的安全性, 可以使用serial queue 来解决访问的安全性.
          // serial queue缺陷: 后面的任务必须等待前面的任务执行结束后在执行, 降低数据的读取效率
          // 如果只是读取数据 适合使用 concurrent queue









    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值