iOS 多线程
【理论】
进程:一个可执行的程序,每一个进程都有自己独立的虚拟内存空间
任务:一块可执行的代码
线程:指的是一个独立的代码执行路径,线程是代码执行路径的最小单位
并行:是CPU的多核芯同时执行多个任务
并发:是单核CPU交替执行两个任务
队列:
串行队列:(类似前后一个个站队跑步)队列中的任务只会顺序执行
dispatch_queue_t q = dispatch_queue_create("....", dispatch_queue_serial);
并行队列:(类似赛跑)串行是一次只能执行一个任务,并行是一次能执行多个任务
dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);
全局队列:系统自带的,直接拿过来(get)用就可以;与并行队列类似,但调试时,无法确认操作所在队列
dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);
主队列:每一个应用程序对应唯一一个主队列,直接get即可;在多线程开发中,使用主队列更新UI
dispatch_queue_t q = dispatch_get_main_queue();
操作:
dispatch_async 异步操作,会并发执行,无法确定任务的执行顺序;
dispatch_sync 同步操作,会依次顺序执行,能够决定任务的执行顺序;
串行队列同步:操作不会新建线程、操作顺序执行;
串行队列异步:操作需要一个子线程,会新建线程、线程的创建和回收不需要程序员参与,操作顺序执行,是最安全的选择;
并行队列同步:操作不会新建线程、操作顺序执行;
并行队列异步:操作会新建多个线程(有多少任务,就开n个线程执行)、操作无序执行;队列前如果有其他任务,会等待前面的任务完成之后再执行;场景:既不影响主线程,又不需要
顺序执行的操作!
全局队列异步:操作会新建多个线程、操作无序执行,队列前如果有其他任务,会等待前面的任务完成之后再执行;
全局队列同步:操作不会新建线程、操作顺序执行;
主队列异步:操作都应该在主线程上顺序执行的,不存在异步的;
主队列同步:如果把主线程中的操作看成一个大的block,那么除非主线程被用户杀掉,否则永远不会结束;主队列中添加的同步操作永远不会被执行,会死锁;
不同队列嵌套dispathch_sync(同步)任务的结果
串行队列:会死锁,但是会执行嵌套同步操作之前的代码 dispatch_queue_serial
dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", dispatch_queue_serial);
并行队列:都在主线程上执行,不会死锁 dispatch_queue_concurrent
dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", dispatch_queue_concurrent);
全局队列:都在主线程上执行,不会死锁 dispatch_queue_priority_default
dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);
主队列:直接死锁 dispatch_get_main_queue();
dispatch_queue_t q = dispatch_get_main_queue();
dispatch_sync同步应用开发场景
阻塞并行队列的执行,要求某一操作执行后再进行后续操作,如用户登录
确保块代码之外的局部变量确实被修改
dispatch_queue_t q = dispatch_queue_create("com.testGCD", dispatch_queue_concurrent); __block BOOL isLogin = NO; dispatch_sync(q, ^{ isLogin = YES; }); if(isLogin) { NSLog(@"登录完成的处理 %@", [NSThread currentThread]); }
【iOS 线程简述】
iOS 线程有三种实现的方式:三者从上到下 越来越抽象,使用越来越简单。
NSThread
(1)使用nsthread对象建立一个线程非常方便
(2)但是!要使用nsthread管理多个线程非常困难,不推荐使用
(3)技巧!使用[nsthread currentthread]跟踪任务所在线程,适用于这三种技术
NSOperation/NSOperationQueue
(1)是使用gcd实现的一套objective-c的api
(2)是面向对象的线程技术
(3)提供了一些在gcd中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系
GCD (grand central dispatch)
(1)是基于c语言的底层api
(2)用block定义任务,使用起来非常灵活便捷
(3)提供了更多的控制能力以及操作队列中所不能使用的底层函数
一、NSThread
创建方式:
方法一:直接创建线程并且开始运行线程
[NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];
方法二:先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; [thread start];
方法三:隐式创建一个线程
[NSObject performSelectorInBackground:@selector(downloadImage:) withObject:kURL];
线程之间的通讯
当下载完成以后 需要通知主线程去刷新UI
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; /** performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如: performSelector:onThread:withObject:waitUntilDone: **/
举个栗子:
#pragma mark - NSThread的使用 - (void)createNewThread { /** 参数的意义: selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。 target :selector消息发送的对象 argument:传输给target的唯一参数,也可以是nil **/ //方法一:直接创建线程并且开始运行线程, [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL]; //方法二:先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; [thread start]; //方法三:隐式创建一个线程 [self performSelectorInBackground:@selector(downloadImage:) withObject:kURL]; } - (void)downloadImage:(NSString *)url { NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]]; UIImage *image = [[UIImage alloc]initWithData:data]; if(image == nil){ NSLog(@"image error"); }else{ [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; /** performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如: performSelector:onThread:withObject:waitUntilDone: **/ } } - (void)updateUI:(UIImage*)image { self.imageView.image = image; }
二、NSOperation
使用方式有两种:
方法一:使用两个子类:NSInvocationOperation 和 NSBlockOperation。
方法二:新建继承NSOperation的子类
#import "ViewController.h" #define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:operation]; // Do any additional setup after loading the view, typically from a nib. } -(void)downloadImage:(NSString *)url{ NSLog(@"url:%@", url); NSURL *nsUrl = [NSURL URLWithString:url]; NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl]; UIImage * image = [[UIImage alloc]initWithData:data]; [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; } -(void)updateUI:(UIImage*) image{ self.imageView.image = image; }
第二种方式继承NSOperation
在.m文件中实现main方法,main方法编写要执行的代码即可。
如何控制线程池中的线程数?
队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
通过下面的代码设置:
[queue setMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。
三、GCD
简述:
Grand Central Dispatch 简称(GCD),以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。
这建立在任务并行执行的线程池模式的基础上的。
原理:
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,它是在应用程序主线程上执行任务的。
1、常用的方法dispatch_async
为了避免界面在处理耗时的操作时卡死,例如:读取网络数据, 数据库读写等,我们会在非主线程线程中(非 Main dispatch queue 等串行队列)处理这些操作,然后通知主线程更新界面。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 耗时的操作 dispatch_async(dispatch_get_main_queue(), ^{ // 更新界面 }); });
实际应用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.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; }); } });
使用的话看起来,简单清晰的很多。
注:
系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。不需要去创建。只需要通过使用函数dispath_get_global_queue 去get获得队列,如下:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
这里也用到了系统默认就有一个串行队列main_queue:
dispatch_queue_t mainQ = dispatch_get_main_queue();
虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retain或release。
2、dispatch_group_async的使用
可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。
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);
打印结果:
2012-09-25 16:04:16.737 gcdTest[43328:11303] group1 2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2 2012-09-25 16:04:18.738 gcdTest[43328:13003] group3 2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi
3、dispatch_barrier_async
在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", 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_async1 dispatch_async2 dispatch_barrier_async dispatch_async3
4、dispatch_apply
执行某个代码片段n次。
dispatch_apply(5, globalQ, ^(size_t index) { // 执行5次 });
四、串行同步、 串行异步、 并行同步、 并行异步、
串行同步: DISPATCH_QUEUE_SERIAL
串行异步: DISPATCH_QUEUE_CONCURRENT
并行同步:
并行异步:
五、 dispatch_sync 导致的线程死锁
死锁:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"=================4"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"=================5"); }); NSLog(@"=================6"); }
上面代码的分析:
主线程执行 viewDidLoad 检测到 dispath_sync 同步任务后,主线程被阻塞进入等待,
dispath_sync 同步任务 block 加到了dispatch_get_main_queue() 队列中,等待队列上个任务完成以后就开始执行 ,
然而dispatch_get_main_queue()队列中 的上一个任务就是主线程的任务,主线程被阻塞了,根本无法完成,
结果:导致了 dispath_sync block 任务等待主线程任务完成,主线程又被阻塞了等待 dispath_sync block 任务完成,就是死锁了。
不会死锁:
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"=================1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"=================2"); });
NSLog(@"=================3"); }); }
上面代码的分析:
dispath_sync 阻塞并等待的是一个异步队列中的新线程, 那么结果就是 新的线程等待dispath_sync,dispath_sync 等待主线程任务。不会死锁。
死锁:
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_main_queue(0, 0), ^{ NSLog(@"=================1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"=================2"); }); NSLog(@"=================3"); }); }
不会死锁:
dispatch_queue_t queue = dispatch_queue_create("serial", nil); dispatch_sync(queue, ^(void){ NSLog(@"这个也不会死锁"); });
分析一下代码,向名为serial的串行队列添加任务后,GCD自动创建了一个新的线程,在这个线程中执行block方法。在这个过程中,主线程和新的线程都是阻塞的,但是并不会导致死锁。