iOS中三种多线程编程技术分别是:
- NSThread
- Cocoa NSOperation
- GCD (全称: Grand Central Dispatch)
这三种编程方式从上到下, 抽象度层次从低到高, 抽象度越高的使用越简单, 也是Apple最推荐使用的.
各自优缺点:
- NSThread:
优点: NSThread 比其他两个轻量级
缺点: 需要自己管理线程的生命周期, 线程同步. 而线程同步对数据的枷锁会有一定的系统开销.
- NSOperation
优点: 不需要关心线程管理, 数据同步的事情, 可以把经理放在自己需要执行的操作上. Cocoa operation 相关的类是 NSOperation, NSOperationQueue.
NSOperation 是个抽象类, 使用它必须用它的子类, 可以实现它挥着使用它自定义的两个子类: NSInvocationOperation和 NSBlockOperation.
创建NSOperation子类的对象, 把对象添加到NSOPerationQueue对队列里执行.
- GCD
Grand Central Dispatch(GCD)是Apple开发的一个多核编程的解决方法, 在iOS4.0 开始之后才能使用. GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术, 现在的iOS系统升级 不用担心技术不能使用.
NSThread的使用
NSThread 有两种直接创建方式
一. 实例方法 (先创建线程并要有个开始start指令,这里可设置线程的优先级等线程信息)
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
二. 类方法 (直接创建线程并开始运行线程)
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
参数的意义:
- selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
- target :selector消息发送的对象
- argument:传输给target的唯一参数,也可以是nil
不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
线程间通讯
线程下载完图片后怎么通知主线程更新界面呢?
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:
performSelector:onThread:withObject:waitUntilDone:
线程同步
如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
线程的顺序执行
他们都可以通过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。
使用****NSCondition****,实现多线程的同步,即可实现生产者消费者问题。
基本思路是,首先要创建公用的****NSCondition****实例。然后:
消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。
这里需要注意****wait****和****signal****的问题:
1: 其实,wait函数内部悄悄的调用了unlock函数(猜测,有兴趣可自行分析),也就是说在调用wati函数后,这个NSCondition对象就处于了无锁的状态,这样其他线程就可以对此对象加锁并触发该NSCondition对象。当NSCondition被其他线程触发时,在wait函数内部得到此事件被触发的通知,然后对此事件重新调用lock函数(猜测),而在外部看起来好像接收事件的线程(调用wait的线程)从来没有放开NSCondition对象的所有权,wati线程直接由阻塞状态进入了触发状态一样。这里容易造成误解。
2: wait函数并不是完全可信的。也就是说wait返回后,并不代表对应的事件一定被触发了,因此,为了保证线程之间的同步关系,使用NSCondtion时往往需要加入一个额外的变量来对非正常的wait返回进行规避。
3: 关于多个wait时的调用顺序,测试发现与wait执行顺序有关。具体请查阅文档。
其它同步:
可使用指令 @synchonized 来简化 NSLock 的使用, 这样不必显示创建NSLock, 加锁并解锁相关代码.
除外还有比如:循环锁 NSRecursiveLock, 条件锁 NSConditionLock, 分布式锁 NSDistributedLock等等
Cocoa NSOperation 的使用
使用 NSOperation 的方式有两种,
一种是用定义好的两个子类:
- NSInvocationOperation 和 NSBlockOperation.
- 另一种是继承NSOperation
NSOperation 和 java.lang.Runnable接口很相似. 和其一样, NSOperation 也是设计用来扩展的, 只需继承重写NSOperation的一个main, 相当于 java 中Runnable的 Run方法. 然后把NSOperation子类的对象放入 NSOperationQueue队列中, 该队列就会启动并开始处理它.
默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:
1> 自定义子类继承NSOperation,实现内部相应的方法
2> NSBlockOperation
3>NSInvocationOperation
先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。
1.****首先介绍自定义****NSOperation****:
NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。
#import <Foundation/Foundation.h>
@protocol NSDefineOprationDelegate <NSObject>
- (void) handleDelegate;
@end
@interface NSDefineOpration : NSOperation
@property (nonatomic, assign) id <NSDefineOprationDelegate> delegate;
- (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate;
@end
实现文件里:
#import "NSDefineOpration.h"
@implementation NSDefineOpration
- (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate{
if(self = [super init]){
self.delegate = delegate;
}
return self;
}
- (void)main{
@autoreleasepool {
//do something
sleep(15);
NSLog(@"op1........handle...... on thread num :%@",[NSThread currentThread]);
if([self.delegate respondsToSelector:@selector(handleDelegate)])
{
[self.delegate performSelector:@selector(handleDelegate) withObject:nil];
}
}
}
@end
这里的sleep(15)主要用来做一些延时的操作,比如网络下载等。
调用:
- (void)oprationTest
{
NSDefineOpration *op1 = [[NSDefineOpration alloc] initWithDelegate:self];
op1.completionBlock = ^(){
NSLog(@"op1........OK !!");
};
[op1 start];
}
从执行结果可以看出,因为在实现的main函数里没有使用异步线程处理,导致直接阻塞了主线程1,所以使用这种方式一定注意main函数里操作时间过长导致主线程阻塞问题。耗时比较长的都放到其他线程里处理。
2.****接下来介绍****NSBlockOperation
第一种使用NSBlockOperation的方式 block类方法
NSLog(@"block start");
NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(15);
NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
}];
[bop2 setCompletionBlock:^{
NSLog(@"bop2........OK !!");
}];
[bop2 start];
首先初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作 调用了start方法,紧接着会马上执行Block中的内容
这里还是在当前线程同步执行操作,并没有异步执行,阻塞主线程。
第二种使用NSBlockOperation的方式: alloc 实例方法
- (void)blockOprationTest
{
NSLog(@"block start");
NSBlockOperation * op2 = [[NSBlockOperation alloc] init];
[op2 addExecutionBlock:^{
sleep(10);
NSLog(@"op2.....handle..... on 10 hread num%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
sleep(6);
NSLog(@"op2.....handle..... on 6 thread num%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
sleep(4);
NSLog(@"op2.....handle..... on 4 thread num%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
sleep(8);
NSLog(@"op2.....handle..... on 8 thread num%@",[NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
sleep(1);
NSLog(@"op2.....handle..... on 1 thread num%@",[NSThread currentThread]);
}];
[op2 setCompletionBlock:^{
NSLog(@"op2........OK !!");
}];
[op2 start];
//bop2
NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(15);
NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
}];
[bop2 setCompletionBlock:^{
NSLog(@"bop2........OK !!");
}];
[bop2 start];
}
分析下结果:
首先看到了有1和2两个线程,线程2在56秒的时候开始执行6秒的操作,接下来执行4,1秒结束时间为07秒。线程1在56的时候开始执行10秒的操作,接下来执行8秒,结束时间为14秒。最后执行Bop2的15秒操作至29秒。时间看起来没有问题。为什么会启用2个线程而不是3个或者更多?
3.****接下来介绍****NSInvocationOperation
- (void)invocationOperation
{
NSInvocationOperation * op3 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOpDelegate) object:nil];
[op3 setCompletionBlock:^{
NSLog(@"op3........OK !!");
}];
[op3 start];
}
selector函数:
- (void)handleInvoOpD
{
sleep(5);
NSLog(@"op3.....handle..... on thread num :%@",[NSThread currentThread]);
}
NSInvocationOperation比较简单,就是继承了NSOperation,区别就是它是基于一个对象和selector来创建操作,可以直接使用而不需继承来实现自己的操作处理。
4.****最后介绍下****NSOperationQueue
把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
eg:
- (void)handleOpqueue
{
NSOperationQueue *qu = [[NSOperationQueue alloc] init];
NSBlockOperation * bkOp1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(10);
NSLog(@"bkOp1.....handle.....on thread num%@",[NSThread currentThread]);
}];
[bkOp1 setCompletionBlock:^{
NSLog(@"bkOp1........OK !!");
}];
NSBlockOperation * bkOp2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(2);
NSLog(@"bkOp2.....handle.....on thread num%@",[NSThread currentThread]);
}];
[bkOp2 setCompletionBlock:^{
NSLog(@"bkOp2........OK !!");
}];
NSBlockOperation * bkOp3 = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"bkOp3.....handle.....on thread num%@",[NSThread currentThread]);
}];
[bkOp3 setCompletionBlock:^{
NSLog(@"bkOp3........OK !!");
}];
NSBlockOperation * bkOp4 = [NSBlockOperation blockOperationWithBlock:^{
sleep(10);
NSLog(@"bkOp4.....handle.....on thread num%@",[NSThread currentThread]);
}];
[bkOp4 setCompletionBlock:^{
NSLog(@"bkOp4........OK !!");
}];
NSBlockOperation * bkOp5 = [NSBlockOperation blockOperationWithBlock:^{
sleep(5);
NSLog(@"bkOp5.....handle.....on thread num%@",[NSThread currentThread]);
}];
[bkOp5 setQueuePriority:NSOperationQueuePriorityHigh];
[bkOp5 setCompletionBlock:^{
NSLog(@"bkOp5........OK !!");
}];
NSInvocationOperation *invoOp6 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOp) object:nil];
[invoOp6 setCompletionBlock:^{
NSLog(@"invoOp6........OK !!");
}];
[invoOp6 setQueuePriority:NSOperationQueuePriorityHigh];
[qu setMaxConcurrentOperationCount:2];
[qu addOperation:bkOp3];
[qu addOperation:bkOp2];
[qu addOperation:bkOp1];
[qu addOperation:bkOp4];
[qu addOperation:bkOp5];
[qu addOperation:invoOp6];
}
在设置了bkop5以及invOp6的优先级为高时,他们会优先执行,当然这个优先时相对,是相对正在排队的,不包括已经正在执行的。
总结:NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue都比较简单,NSOperation、NSBlockOperation、NSInvocationOperation单个都是表示一种操作,而NSOperationQueue是一个可以包含多个NSOperation的队列,可以自己在多个线程处理,只要加入队列之后,我们就不用去操作,直到Callback或者完成。
** 3. GCD****的介绍和使用**
介绍: Grand Central Dispatch 简称(GCD)是苹果公司开发的技术, 以优化的应用程序支持多核心处理器和其他的对称处理系统的系统. 这简历在任务并行执行的线程池模式的基础上的. 它首次发布在Mac OS X 10.6, iOS 4及以上可用.
设计: GCD的工作原理是: 让程序平行排列的特定任务, 根据可用的处理资源, 安排他们在任何可用的处理器核心上执行任务.
一个任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现, 不过这样可以让程序员不用关注实现的细节.
GCD中的FIFO队列称为Dispatch Queue, 它可以保证先进来的任务先得到执行.
Dispatch Queue分下面三种:
Serial
又称为private dispatch queues, 同时只执行一个任务. serial queue 通常用于同步访问特定的资源或数据. 当你创建多个 Serial Queue 时, 虽然他们各自是同步执行的, 但 Serial Queue 与 Serial queue之间是并发执行的.
Concurrent
又称为global dispatch queue, 可以并发执行多个任务, 但执行完成的顺序是随机的.
Main dispatch queue
它是全局可用的serial queue, 它是在应用程序主线程上执行任务的.
常用的方法 dispatch_async
为了避免界面在处理耗时的操作时卡死, 比如读取网络数据, IO, 数据库读写等, 我们会在另外一个线程中处理这些操作, 然后通知主线程更新界面.
用GCD实现这个流程的操作比前面介绍的 NSThread NSOperation 的方法都要简单. 代码框结构如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://image.tianjimedia.com/uploadImages/2012/233/38/H439I0N71ARI.jpg"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
}
});
相比前两种, GCD会自动根据任务在多喝处理器上分配资源, 优化程序.
系统给每一个应用程序提供了三个 concurrent dispatch queues. 这三个并发调度队列是全局的, 他们只是优先级的不同, 因为是全局的, 我们不需要去创建,. 我们只需要通过使用函数dispath_get_global_queue去得到队列, 如下:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里也用到了系统默认就有一个串行队列main_queue:
dispatch_queue_t mainQ = dispatch_get_main_queue();
虽然dispatch queue 是引用计数对象, 但是以上两个都是全局的队列, 不用retain 或 release.
dispatch_group_async的使用
dispatch可以监听一组任务是否完成, 完成后得到通知执行其他的操作. 这个方法很有用, 比如你执行三个下载任务, 当三个任务都下载完成后你才通知界面说完成的了, 举例:
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, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUI”);
});
dispatch_release(group);
dispatch_group_async是异步的方法,运行后可以看到打印结果:
每隔一秒打印一个,当第三个任务执行后,upadteUI被打印。
dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后他才执行, 而且它后面的任务等它执行完成之后才会执行
举例:
dispatch_queue_t queue = dispatch_queue_create(“haha.com”, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
// 先执行完前面的在执行后面的
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
dispatch_apply
执行某个代码片段N次.
dispatch_apply(5, globalQ, ^(size_t index)) {
// 执行5次
}