iOS边城之多线程并发编程

http://blog.csdn.net/victormokai/article/details/42419483


参考:官方 OS X和iOS中的多线程技术

基本每门语言都有多线程这个技术点,多线程是为了实现并发执行,可以理解为一个系统进程是由一个或多个线程组成的。iOS中创建线程的方式简单到可以直接调用对象的方法来实现,下面我们来看看。

 

调用NSObject方法实现多线程

NSObject提供了以 performSelector为前缀的一系列方法。它允许用户在指定的线程、什么时间执行某个方法的调用。实现了多线程编程最简洁的方式


在当前线程中执行方法

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (id)performSelector:(SEL)aSelector;  
  2. - (id)performSelector:(SEL)aSelectorwithObject:(id)object;  
  3. - (id)performSelector:(SEL)aSelectorwithObject:(id)object1 withObject:(id)object2;  
  4. - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;//delay为延迟多少秒调用  
在主线程中执行方法
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;  
  2. - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait;  

在指定线程中执行方法

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)waitmodes:(NSArray *)array;  
  2. - (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;  
在后台线程中执行方法

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)performSelectorInBackground:(SEL)aSelectorwithObject:(id)arg;  
取消延迟调用的方法

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelectorobject:(id)anArgument;  
  2. + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;  

上述方法中需要注意的是

指定执行的方法(但传入的参数数量有限制,最多两个);

iOS默认的主线程为UI线程,更新UI的代码需要在主线程中执行,这点与Android相同,一般在非主线程需要进行UI更新,可以调用performSelectorOnMainThread来实现

如果指定在后台线程执行,则会自动创建一个线程去调用方法

如果使用了waitUntilDone:相关的方法来延迟调用方法,在期间如果不想调用了可以取消掉

 

NSThread 

以对象的方式来创建线程,它是线程的一个轻量级实现,有点类似于Java的Thread类。在执行一些简单任务时,NSThread很有用,比如线程休眠、线程同步、线程间通信

 

三种创建方式

 立即执行

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [NSThreaddetachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:selfwithObject:nil];   

 通过 start执行

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSThread* myThread = [[NSThread alloc]initWithTarget:self  
  2.                                       selector:@selector(myThreadMainMethod:)  
  3.                                       object:nil];  
  4. [myThread start];  

通过继承方式

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @interface TalkService :NSThread  
  2. @end  
  3. @implementationTalkService  
  4.    
  5. -(void)main{  
  6.    //要执行的代码  
  7.    NSLog(@"哄");  
  8. }  
  9. @end  
  10.    
  11. TalkService *talk = [TalkService new];  
  12. [talk start];  


相关方法

开启线程 - (void)start;

停止线程    

- (void)cancel;  

+ (void)exit;与前者的区别在于,它是强制性的,不会给你线程清理任何资源的机会

休眠线程

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

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

获取主线程 [NSThread mainThread];

判断当前线程是否为主线程  [NSThread isMainThread]

获取当前线程 [NSThread currentThread];

 

线程间同步

涉及到线程间同步仍然需要配合使用NSLock,NSCondition 或者 @synchronized。

一个简单例子,两个人同时一起抢火车票,但是票只有最后一张了,看谁先抢到,当然这里的抢票规则完全靠哪个线程被先执行谁就先拿到票,话说我现在都还没抢到高铁票鄙视,过年又得骑车回家啦尴尬

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @interface TestNSObjectThread : NSObject  
  2. @property(nonatomic,strongNSLock *lock;  
  3. @property(nonatomicint count;  
  4. -(void)test;  
  5. @end  
  6.   
  7. @implementation TestNSObjectThread  
  8. -(void)test{  
  9.     self.lock = [[NSLock alloc] init];  
  10.     self.count = 1;  
  11.       
  12.     NSThread *mokai = [[NSThread alloc] initWithTarget:self selector:  
  13.                         @selector(run) object:nil];  
  14.     [mokai setName:@"mokai"];  
  15.     [mokai start];  
  16.       
  17.     NSThread *gongkai = [[NSThread alloc] initWithTarget:self selector:  
  18.                        @selector(run)  object:nil];  
  19.     [gongkai setName:@"gongkai"];  
  20.     [gongkai start];  
  21.       
  22.     [NSThread sleepForTimeInterval:1];//因为为控制台项目,所以要防止主线程先执行完结束  
  23. }  
  24.   
  25. -(void)run{  
  26.     [self.lock lock];//给lock锁上,这里可以通俗地理解为一间房一次只能进一个人,每个进去时把门反锁,出来时把门打开  
  27.     if(self.count > 0){  
  28.         self.count--;  
  29.         NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);  
  30.     }  
  31.     [self.lock unlock];//解锁  
  32. }  
  33. @end  

上述run方法也可以用@synchronized来代替

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @synchronized(self){  
  2.     if(self.count > 0){  
  3.        self.count--;  
  4.         NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);  
  5.     }  
  6. }  

传进去的self为对象锁,可以理解为该对象做了一个标识“这个对象我在用,你等着!我用完你再用。”

好了,多运行多几次,控制台会随机的输出:

2015-01-06 11:38:18.119 TestMultithreading[1266:77053] mokai抢到一张火车票

2015-01-06 11:38:26.028 TestMultithreading[1269:77140] mokai抢到一张火车票

2015-01-06 11:38:32.259 TestMultithreading[1273:77236] mokai抢到一张火车票 

2015-01-06 11:38:38.450 TestMultithreading[1276:77311]gongkai抢到一张火车票

 

GCD 

参考:iOS多线程GCD

GCD(GrandCentral Dispatch),基于系统多核编程、C函数式编程、结合Blocks配合使用


简单一例

在上面我们可以通过performSelector系列方法来创建线程

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];  

GCD中,我们是这样写的

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_queue_t t = dispatch_get_main_queue();  
  2. dispatch_async(t, ^{  
  3.     [self run];  
  4. });  
虽然看起来没前者那么简洁,尤其对于面向对象开发者来说,函数式编程怎么看怎么不爽。但是官方说了, GCD 是基于多核的,性能杆杆的,强烈建议能用 GCD 解决尽量使用 GCD 解决。不管你用不用,反正我平常都是用的本文第一种“调用NSObject方法实现多线程”

分发队列

执行任务的容器,可以提供各种自定义操作,如并行,串行

GCD中有三种队列:

1、主线程队列

主线程队列中的任务会在应用的主线程中执行。一般用于UI更新操作

dispatch_queue_t queue =dispatch_get_main_queue();

 

2、并行队列

串行分发队列又被称为全局分发队列,由系统创建三个不同优先级的dispatchqueue,也按顺序执行队列中的任务,但是顺序开始的多个任务会并发同时执行。并发分发队列常用于管理并发任务。

dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

 

3串行队列

串行分发队列又被称为私有分发队列,按顺序执行队列中的任务,且同一时间只执行一个任务。但各个串行队列之间是并发的。

当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。另外,通过这种方式创建的队列需要我们手动调用dispatch_release释放

dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue",NULL);//注意,此处传的是C字符不是@""

 

任务执行

同步执行,主线程会被阻塞,可以理解为Block的执行是主线程上进行的

dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);

异步执行,与同步执行相反,主线程不会被阻塞

dispatch_async(dispatch_queue_t queue,dispatch_block_t block);

 

加载网络图片的例子

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  2.     NSData * data = [NSData dataWithContentsOfURL:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];  
  3.     UIImage * image = [UIImageimageWithData:data];  
  4.     //加载UI的操作,一般放在主线程进行  
  5.     dispatch_async(dispatch_get_main_queue(), ^{  
  6.        self.imageView.image = image;  
  7.     });  
  8. });  

实际中我们会使用了嵌套GCD来完成功能,先用后台线程执行任务,然后调用主线程执行UI更新,注意,GCD的嵌套也不能乱嵌,否则会出现死锁情况,像如下代码,会立即死锁

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue"NULL);  
  2. dispatch_sync(queue, ^{  
  3.     NSLog(@"1");  
  4.     dispatch_sync(queue, ^{  
  5.        NSLog(@"2");  
  6.     });  
  7. });  

常见场景

 1、在主线程执行代码

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_async(dispatch_get_main_queue(),^{  
  2.     // updateui  
  3. });  
2 、延时执行
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));//表示当前时间后的5秒  
  2. dispatch_after(time, dispatch_get_main_queue(), ^(void){  
  3.    //执行代码  
  4. });  
3、使用 dispatch_once 实现线程安全单一执行要求

GCD 的 dispatch_once能够在保证自应用运行到暂停 block只执行一次。以下是编程中经常用到的单例模式,这也是现在OC中推荐的一种方法

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. + (id)sharedInstance {  
  2.     static TestNSObjectThread*sharedInstance = nil;  
  3.    static dispatch_once_t onceToken ;  
  4.     dispatch_once(&onceToken, ^{  
  5.        sharedInstance = [[self alloc] init];  
  6.     });  
  7.     return sharedInstance;  
  8. }  


操作队列NSOperation  

参考:iOS多线程编程之NSOperation和NSOperationQueue的使用

NSOperation,类似于javaRunable接口,设计用来扩展的,自带有二个扩展,NSInvocationOperation(基于Selector调用)、NSBlockOperation(基于Block调用),如果想自定义只需重写main方法即可(java的run方法),使用与前面介绍的NSThread差不多NSOperationQueue队列,用来管理执行Operations的容器

 

NSInvocationOperation 

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSInvocationOperation*operation = [[NSInvocationOperation alloc]initWithTarget:self  
  2.                                                                     selector:@selector(downloadImage:)  
  3.                                                                      object:kURL];  
  4.    
  5. NSOperationQueue *queue= [[NSOperationQueue alloc]init];  
  6. [queue addOperation:operation];  

NSBlockOperation 

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{  
  2.    //代码...  
  3. }];  
  4. NSOperationQueue *queue= [[NSOperationQueue alloc]init];  
  5. [queue addOperation:operation];  

细粒化控制

通过NSOperationNSOperationQueue的配合使用,可实现复杂的流程化任务。


1、NSOperation依赖对象,通过addDependency来添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系

唯一的限制是不能创建环形依赖,比如A依赖BB依赖A,这是错误的

依赖关系会影响到NSOperation对象在queue中的执行顺序,默认是按照添加顺序执行的

 

2、执行修改Operations的执行顺序

依赖关系相对优先级:优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operationqueue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。

 

3、设置队列的最大并发操作数量

setMaxConcurrentOperationCount:默认为

如果设置为1就表示为串行化operationqueue

 

4、等待Options完成

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [operation waitUntilFinished]; //单个operation  
  2. [queue waitUntilAllOperationsAreFinished];  //queue中全部operation  

5、暂停和继续queue

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [queue setSuspended:YES]; // 暂停queue    
  2. queue setSuspended:NO]; // 继续queue    
 

重复任务NSTimer、NSRunLoop

有时应用中需要用到定时刷新的功能,如抢票APP需要频繁地请求12306,用之前的GCD也能实现效果,但是过程太痛苦了,那么苹果提供了NSTimer类来帮助我们完成重复任务。

NSTimerNSOperation设计一样,可以通过自身来启动任务,也可以放到NSRunLoop里面执行

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行  
  2. [timer fire];//立即执行  

通过这种方式创建的NSTimer结果只会调用一次refresh方法,无论repeatsYES还是NO都会如此,如果想重复执行则需要添加到NSRunLoop中,如下

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行  
  2. NSRunLoop *loop = [NSRunLoop mainRunLoop];  
  3. [loop addTimer:timer forMode:NSDefaultRunLoopMode];//增加到运行中  
  4. [loop run];//启动执行  

这里的代码每隔1秒就会调用一次log方法,其实NSTimer提供了一个快捷方法,上述代码也可以写成

[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];  
  2. [timer fire];  

停止任务可以通过[timer invalidate]来执行,需要注意的是NSTimer没有提供暂停的接口,如果想实现暂停的功能可以先invalidate然后重新启动一个NSTimer



    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值