多线程NSThread的使用
NSThread每个NSThread对象对应一个线程,轻量级。
NSThread:优点:NSThread比其他俩个轻量级,使用简单。
缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销。
NSThread的几种创建方式
//方式一:利用perform开启多线程,并且执行方法threadAction
// [self performSelectorInBackground:@selector(threadAction) withObject:@"thread"];
//方式二:
// NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@"text"];
// thread.name = @"thread1";
// //开启线程
// [thread start];
//方式三:开启新的线程,并且执行
[NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@"thread2"];
对比主线程与多线程在执行上的先后顺序:
在viewDidLoad里写一个for循环。
for (int i=0; i<50; i++) {
NSLog(@"主线程:%d",i);
}
在多线程的threadAction:方法里也同样写一个for循环
for (int i=0; i<50; i++) {
NSLog(@"多线程:%d",i);
}
打印结果:
通过俩次打印结果,我们知道他们是没有先后顺序的,而且每次打印都不同。
有几个常用的方法我们可能会用到:
//获取当前线程
NSThread *thread = [NSThread currentThread];
//判断当前是否在多线程
[NSThread isMultiThreaded]
//判断当前是否在主线程
[NSThread isMainThread]
//让当前线程睡眠几秒
[NSThread sleepForTimeInterval:3];
//回到主线程
// [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]
思考:我们为什么要使用多线程?
总结: 提高CPU的利用率,让程序更加流畅。如果我们把所有的任务都放在主线程会造成主线程阻塞。比如说当你在加载较多的图片时,你的textView是不能滚动的。下面我们就针对这方面写一个demo.
首先我们给UIimageView添加一个类目,用来让其能够加载网络图片
类目的.h文件
#import@interface UIImageView (cache) //为UIImageView写一个添加网络图片的方法 - (void)setimage:(NSString *)str; @end
类目的.m文件
#import "UIImageView+cache.h" @implementation UIImageView (cache) //如果这样直接写方法,在主线程,当我们在加载网络时不能滑动TextView - (void)setimage:(NSString *)str { NSURL *url = [NSURL URLWithString:str]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image = [UIImage imageWithData:data]; } @end
类目写好了,让我们用起来吧。
viewController里的代码如下:
#import "ViewController.h" #import "UIImageView+cache.h" @interface ViewController () { UIImageView *_image; NSMutableArray *arr; } - (IBAction)click:(UIButton *)sender; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; arr = [NSMutableArray array]; //创建八行六列的UIImageView for (int i = 0; i < 6; i ++) { for (int j = 0 ; j < 8; j ++) { _image = [[UIImageView alloc]initWithFrame:CGRectMake(i * 62, j * 62 , 60, 60)]; _image.backgroundColor = [UIColor yellowColor]; [self.view addSubview:_image]; //将创建好的UIImageView放进可变数组。 [arr addObject:_image]; } } } - (IBAction)click:(UIButton *)sender { for (UIImageView *imageview in arr) { //利用分类给数组里的UIImageView添加图片 [imageview setimage:@"http://img31.mtime.cn/pi/2013/03/08/144644.81111130_1280X720.jpg"]; } } @end
这样的运行结果是:
那么为了解决这样阻塞主线程的情况
我们把分类的方法该为:
- (void)setimage:(NSString *)str { //开启一个多线程,并且把str通过创建多线程传递到多线程的任务中(注:这里的字符串为网络图片的地址) [NSThread detachNewThreadSelector:@selector(thredAction:) toTarget:self withObject:str]; } //多线程的任务 - (void)thredAction:(NSString *)str { //将字符串转换成URL NSURL *url = [NSURL URLWithString:str]; //将url转化成data NSData *data = [NSData dataWithContentsOfURL:url]; //注意:UI的修改只能放在主线程 所以写在这里还是错误 //self.image = [UIImage imageWithData:data]; //回到主线程 [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:YES]; }
这样就可以解决线程阻塞的问题了。
多线程NSOperation的使用
NSOperation/NSOperationQueue面向对象的线程技术。
NSOperation:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。NSOpertion是面向对象的。
NSOperation的创建以及常用的方法:
//创建线程队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //关闭暂停队列,用于让所有的线程加入队列后在执行任务 [queue setSuspended:YES]; //设置最大并发数, queue.maxConcurrentOperationCount = 2; //创建线程 NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation1:) object:@"operation1"]; //设置线程优先级 operation1.queuePriority = NSOperationQueuePriorityNormal; //创建第二个线程 NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2:) object:@"operation2"]; //设置线程优先级 operation2.queuePriority = NSOperationQueuePriorityVeryHigh; //将线程加入队列 [queue addOperation:operation1]; [queue addOperation:operation2]; //用Block创建多线程 [queue addOperationWithBlock:^{ //注意这里我们要将任务放进自动释放池,因为这段代码不在(主线程)中 @autoreleasepool { for (int i = 0; i < 10; i ++) { NSLog(@"operation3%d",i); } } }]; //开始队列 [queue setSuspended:NO];
说完创建方式及常用方法,属性。也来说说其解决主线程阻塞的问题
例子跟上篇文章大致是一样的。
类目中的主要代码:
#import "UIImageView+cache.h" @implementation UIImageView (cache) - (void)setImages:(NSString *)str { //创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; //设置最大并发 queue.maxConcurrentOperationCount = 1; //创建线程 NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationAction:) object:str]; //将线程加入线程队列 [queue addOperation:operation]; } - (void)operationAction:(NSString *)str { NSURL *url = [NSURL URLWithString:str]; NSData *data = [NSData dataWithContentsOfURL:url]; //在这里我们要修改UI了所以要重新回到主线程 //第一种回主线程的方式 [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:YES]; /* //第二种方式回到主线程 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.image = [UIImage imageWithData:data]; }]; */ } @end viewController中的代码: #import "ViewController.h" #import "UIImageView+cache.h" @interface ViewController () @property (nonatomic, strong)NSMutableArray *dataArr; @end @implementation ViewController - (NSMutableArray *)dataArr { if (_dataArr == nil) { _dataArr = [NSMutableArray array]; return _dataArr; } return _dataArr; } - (void)viewDidLoad { [super viewDidLoad]; for (int i = 0; i < 8; i ++) { for (int j = 0; j < 6; j ++) { UIImageView *imageview = [[UIImageView alloc]initWithFrame:CGRectMake(j * 64, i * 80, 60, 75)]; imageview.backgroundColor = [UIColor yellowColor]; [self.view addSubview:imageview]; [self.dataArr addObject:imageview]; } } } - (IBAction)click:(UIButton *)sender { for (UIImageView *imageview in self.dataArr) { [imageview setImages:@"http://img31.mtime.cn/pi/2013/03/08/144644.81111130_1280X720.jpg"]; } } @end
这样就解决了主线程阻塞的问题。
多线程GCD的使用
GCD――Grand Central Dispatch 是基于C语言的框架,可以充分利用多核,也是苹果官方推荐使用的多线程技术。
GCD是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread,NSOperation的高效和强大的技术,GCD是基于C语言的
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
多线程里面概念性的东西很多也很难记,所以一定要好好理解。还是通过代码,在温习温习吧。相同的代码写多次,虽然有时很枯燥,可每次都会有不同的体会和领悟。走起!
viewController中:
#import "ViewController.h" @interface ViewController () @end @implementation ViewController /* //写之前,再来温习温习。 GCD的使用: 一 :队列 1.串行队列:添加到队列中的任务是一个一个执行的 2.并发(行)队列:添加到队列中的任务是多个同时执行的 3.主队列:里面的任务都是在主线程执行的。 4.全局队列:并行(发)队列 二:同步、异步 1、同步:需要后面的任务等待,不会开启新的线程,会直接使用当前的线程 2、异步:不需要后面的任务等待,会开启新的线程 */
- (void)viewDidLoad {
[super viewDidLoad];
/*---------创建并发队列---------*/
//1.创建并行队列
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
//2.获取全局队列(并行队列)
/*优先级<#long identifier#>
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
*/
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//(1)在并发队列添加异步任务
dispatch_async(queue2, ^{
for (int i = 0; i < 50 ;i ++) {
NSLog(@"1---------1");
}
});
dispatch_async(queue2, ^{
for (int i = 0; i < 50 ;i ++) {
NSLog(@"2---------2");
}
});
打印结果:
通过打印结果我们可以看出:
(1)并发队列异步添加任务会开多条线程,执行顺序是没有先后(同时执行)
看完并发队列的异步添加,让我们再来看看并发队列的同步添加:
dispatch_sync(queue2, ^{ @autoreleasepool { for (int i = 0; i < 50 ;i ++) { NSLog(@"1---------1"); } } }); dispatch_sync(queue2, ^{ @autoreleasepool { for (int i = 0; i < 50 ;i ++) { NSLog(@"2---------2"); } } });
这个打印结果相信不用说,我们都知道肯定是先执行1 在执行2
其实执行问题好回答,关键是此时的任务是在那个线程完成的,相信如果你对同步添加的概念如果十分清楚的话一定能回答对,没有错。这些任务是在主线程中完成的。并不是在queue2中完成的。让我们看图吧,有图有真相
通过以上分析我们可以看出:
(1)在并发队列中同步添加任务,不会开启新的线程,会直接使用当前线程,
(2)并且,我们添加的任务是添加到主线程。并不是表面现象的queue2。
看完并发(行)队列,让我们来看看串行队列吧。
/*---------创建串行----------*/ dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL); //向串行队列异步添加任务 dispatch_async(queue, ^{ for (int i=0; i<50; i++) { NSLog(@"🐷....i:%d",i); } }); dispatch_async(queue, ^{ for (int i=0; i<50; i++) { NSLog(@"🐶....i:%d",i); } });
打印结果:
尽管是异步添加任务,但是该队列是串行队列,所以猪 先被执行,然后才执行狗。
如果此时我们再在viewDidLoad中也就是在狗的for训话下面添加一个for循环的话你能想象出结果吗?
for (int i=0; i<100; i++) {
NSLog(@"🐱....i:%d",i);
}
打印结果:
不难看出猫与(狗和猪)是同时执行的。而(猪和狗)则是猪先执行,狗在执行。例如猪和狗排了一个队,而猫自己排了一个队。
继续介绍:
向串行队列同步添加任务
dispatch_sync(queue, ^{ for (int i=0; i<50; i++) { NSLog(@"🐶....i:%d",i); } }); dispatch_sync(queue, ^{ for (int i=0; i<100; i++) { NSLog(@"🐱....i:%d",i); } }); for (int i=0; i<100; i++) { NSLog(@"🐷....i:%d",i); }
运行结果毫无疑问。狗-----》猫----------》猪;时刻记着同步添加任务,不会开启新的线程,会直接使用当前线程。那么肯定这三个任务也是在当前的主线程。
介绍完这些,还有一个重要的东西那就是死锁
让我们来看一下
获取主队列:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
分配任务:
NSLog(@"任务开始啦!!!"); //同步添加任务 dispatch_sync(mainQueue, ^{ NSLog(@"任务添加"); }); NSLog(@"结束");
毫无疑问第一个打印肯定是 第一行的“任务开始啦!!!”,那么接下来走第三行代码,因为是Block所以第五行的代码要在第九行的打印执行结束后才能被执行(block回调),又因为是同步添加任务,所以第九行代码要在上面的任务执行完才能开始。所以编译器犹豫不知道该执行那一个。会造成死锁。
打印结果:
总结:(1)在使用主队列时,绝对不可以在主队列中同步添加任务,会造成死锁。
(2)并发队列:同步添加不会开启新的线程,异步添加会开启多条线程。
(3)串行队列:同步添加不会开启新的线程,异步添加会开启一条线程。
(4)由(2)和(3)得出结论同步添加任务,不具备开启线程的能力。异步添加任务开启线程的条数由当前队列决定,串行队列开启一条线程,并行队列开启多条。