今天闲来无事,就来总结一下iOS中的多线程开发吧。
iOS有三种多线程编程的技术,分别是:
1.NSThread------每个NSThread对象对应一个线程,量级较轻(真正的多线程)
2.NSOperation------NSOperation/NSOperationQueue 面向对象的线程技术
3.GCD------Grand Central Dispatch(派发) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术
其中,NSOperation和GCD是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题。
以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的,在项目中很多框架技术分别使用了不同多线程技术。
三种多线程技术的对比
1.NSThread
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期、线程同步、睡眠以及唤醒。线程同步对数据的加锁会有一定的系统开销
2.Cocoa NSOperation
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation相关的类是NSOperation, NSOperationQueue.
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。
--NSOperation是面向对象的
3.GCD(全优点)
Grand Central dispatch(GCD)是Apple开发的一个多核编程的解决方案。在iOS4.0开始之后才能使用。GCD是一个替代NSThread, NSOperationQueue,NSInvocationOperation等技术的很高效强大的技术。
--GCD是基于C语言的,执行效率高
三种多线程技术的实现
一、NSThread的使用(基本已过时)
1.创建使用线程:
类方法直接开启后台线程,并执行选择器方法
// 新建一个线程,调用@selector方法
[NSThread detachNewThreadSelector:@selector(bigDemo) toTarget:self withObject:nil];
成员方法,在实例化线程对象之后,需要使用start执行选择器方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(bigDemo) object:nil];
// 启动start线程
[thread start];
对于NSThread的简单使用,可以用NSObject的performSelectorInBackground替代
// performSelectorInBackground是将bigDemo的任务放在后台线程中执行
[self performSelectorInBackground:@selector(bigDemo) withObject:nil];
或者
//在指定线程中执行,但该线程必须具备run loop。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
2.线程中代码执行完成后,需要回调主线程才能更新UI
//第一种方式
//[self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
//第二种方式
//[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
//第三种方式
[self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
3.其它一些方法
+ (NSThread *)currentThread; //获得当前线程
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //线程休眠
+ (NSThread *)mainThread; //主线程,亦即UI线程了
- (BOOL)isMainThread; + (BOOL)isMainThread; //当前线程是否主线程
- (BOOL)isExecuting; //线程是否正在运行
- (BOOL)isFinished; //线程是否已结束
4.线程之间的同步(多个线程访问同一资源时)涉及到线程间同步仍然需要配合使用NSLock,NSCondition 或者 @synchronized
例子一(NSLock)
-(void)run{
[self.lock lock];//给lock锁上,这里可以通俗地理解为一间房一次只能进一个人,每个进去时把门反锁,出来时把门打开
if(self.count > 0){
self.count--;
NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
}
[self.lock unlock];//解锁
}
例子二(@synchronized)
@synchronized(self){
if(self.count > 0){
self.count--;
NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
}
}
例子三(NSCondition)
[_condition lock];
if(self.count > 0){
self.count--;
NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
}
[_condition signal];
[_condition unlock];
二、NSOperation,面向对象的多线程技
使用步骤:1.实例化操作
// 实例化操作队列
_queue = [[NSOperationQueue alloc] init];
2.NSInvocationOperation例子
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(opAction) object:nil];
// 如果使用start,会在当前线程启动操作
// [op1 start];
// 一旦将操作添加到操作队列,操作就会启动
[_queue addOperation:op1];
3.NSBlockOperation例子
- (IBAction)operationDemo3:(id)sender
{
//下载
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@" , [NSThread currentThread]);
}];
//滤镜
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"滤镜 %@" , [NSThread currentThread]);
}];
//显示
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"更新UI %@" , [NSThread currentThread]);
}];
// 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
// 依赖关系可以跨线程队列实现
// 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
[op2 addDependency:op1];
[op3 addDependency:op2];
//[op1 addDependency:op3];
[_queue addOperation:op1];
[_queue addOperation:op2];
[[NSOperationQueue mainQueue] addOperation:op3];
}
4.其它控制介绍
a.将操作添加到队列NSOperationQueue即可启动多线程执行
[_queue addOperation:op1];
[_queue addOperation:op2];
b.更新UI使用主线程队列
//两方式
[NSOpeationQueue mainQueue] addOperation ^{
};
[[NSOperationQueue mainQueue] addOperation:op3];
c.操作队列的setMaxConcurrentOperationCount,可以设置同时并发的线程数量!
[_queue setMaxConcurrentOperationCount:2];
提示:此功能仅有NSOperation有!
d.使用addDependency可以设置任务的执行先后顺序,同时可以跨操作队列指定依赖关系
// 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
// 依赖关系可以跨线程队列实现
// 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
[op2 addDependency:op1];
[op3 addDependency:op2];
[op1 addDependency:op3];
提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
三、GCD(C语言)
GCD就是为了在“多核”上使用多线程技术,要使用GCD,所有的方法都是dispatch开头的
名词解释:
global全局
queue队列
async异步---把代码块提交给队列后,立即返回
sync同步---把代码块提交给队列后要等代码块执行完才返回,阻塞当前线程
1.关于GCD的队列
a.全局队列(global)--队列内部并行无序(+async异步 +sync同步)
参数:优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 始终是 0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
b.串行队列(Serial)--队列内部串行
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
是创建得到的,不能直接获取,当你创建多个Serial queue时,虽然它们各自内部是串行执行的,但Serial queue与Serial queue之间是并发执行的。
c.并行队列(Concurrent)--队列内部并行FIFO(+async异步 +sync同步)
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT)
d.主队列(Main)--它是全局可用的serial queue,它是在应用程序主线程上执行任务的,只能同步执行
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main - > %@", [NSThread currentThread]);
});
2.GCD简单应用例子
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:<span>kURL</span>];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
_imageView.image = image;
});
}
});
3.GCD的队列应用
a.创建串行队列提交同步任务
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//code 任务一
});
dispatch_sync(queue, ^{
//code 任务二
});
队列中的任务是串行出列的,任务一执行结束后执行任务二,然而sync又是同步任务,会阻塞当前线程,实际上这里造成了死锁。
b.自定义串行队列,提交异步任务
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//code 任务一
});
dispatch_async(queue, ^{
//code 任务二
});
队列的任务是串行出列,任务一执行结束后执行任务二。
c.自定义并行队列,提交同步任务
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
//code 任务一
});
dispatch_sync(queue, ^{
//code 任务二
});
队列的任务是并行出列,顺序按先进先出的顺序执行,既任务一出列后任务二接着出列(但任务二与任务一又是同步的,一般不这么用)d.自定义并行队列,提交异步任务
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//code 任务一
});
dispatch_async(queue, ^{
//code 任务一
});
任务一出列后任务二出列,各任务之间是异步的,不会阻塞当前线程。e.在主队列提交同步任务,阻塞主线程,造成死锁!
dispatch_sync(dispatch_get_main_queue(), ^{
//code
});
f.在主队列提交异步任务
dispatch_async(dispatch_get_main_queue(), ^{
//code任务
});
g.在全局队列提交同步任务
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
//code
});
h.在全局队列提交异步任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//code
});
dispatch_get_gloabal_queue 的第一个参数为枚举类型(默认为0),决定任务的优先级 ,第二个参数为Apple保留参数,传0
i.总结
队列的类型决定了队列任务的执行方式(主队列是一个串行队列)。一般把会阻塞主线程的任务提交到异步并行队列当中,防止死锁形成。
4.GCD的dispatch_group_async使用
实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。
//此方法可以实现监听一组任务是否完成,如果完成后通知其他操作(如界面更新),此方法在下载附件时挺有用,
//在搪行几个下载任务时,当下载完成后通过dispatch_group_notify通知主线程下载完成并更新相应界面
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:0.09];
NSLog(@"group1");
NSURL * url = [NSURL URLWithString:kURL];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
_image = [[UIImage alloc]initWithData:data];
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:0.09];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:0.09];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
_imageView.image = _image;
});
5.GCD的dispatch_barrier_async使用
- (void)viewDidLoad
{
[super viewDidLoad];
//是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_queue_t queue = dispatch_queue_create("gcd.devdiy.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_async");
});
}
线程与进程的区别
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。
所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。