iOS 面试常问之多线程

Posted 看得见的灿烂,看不见的离殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 面试常问之多线程相关的知识,希望对你有一定的参考价值。

 

本片围绕多线程全面展开叙述。

1、为什么要有多线程/多线程是用来干什么的?

2、多线程是什么?

3、如何创建多线程?

4、多线程在哪些情况下会使用/多线程使用场景?

5、三种多线程的优缺点?

 

1、为什么要有多线程/多线程是用来干什么的?

  每个ios应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。

 

2、多线程是什么?

  多线程是个复杂的概念,按字面意思是同步完成多项任务,提高了资源的使用效率。

  从硬件、操作系统、应用软件不同的角度去看,多线程被赋予不同的内涵,对于硬件,现在市面上多数的CPU都是多核的,多核的CPU运算多线程更为出色;

  从操作系统角度,是多任务,现在用的主流操作系统都是多任务的,可以一边听歌、一边写博客;

  对于应用来说,多线程可以让应用有更快的回应,可以在网络下载时,同时响应用户的触摸操作。

  在iOS应用中,对多线程最初的理解,就是并发,它的含义是原来先做烧水,再摘菜,再炒菜的工作,会变成烧水的同时去摘菜,最后去炒菜。

 

3、如何创建多线程?

  线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue中。下面对这三种方法做详细说明:

1, 使用NSThread创建

  1.1 NSThread 有两种直接创建方式:

- (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

 

  第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息。

   PS:不显式创建线程的方法:

//用NSObject的类方法  performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

 

2, 使用子类化的NSOperation

  使用 NSOperation的方式有两种:

  一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

#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;  
}  
  1. viewDidLoad方法里可以看到我们用NSInvocationOperation建了一个后台线程,并且放到NSOperationQueue中。后台线程执行downloadImage方法。
  2. downloadImage 方法处理下载图片的逻辑。下载完成后用performSelectorOnMainThread执行主线程updateUI方法。
  3. updateUI 并把下载的图片显示到图片控件中。

  另一种是继承NSOperation。

 

如何控制线程池中的线程数?

  队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。

//通过下面的代码设置:
[queue setMaxConcurrentOperationCount:5];
//线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。

 

3, 使用GCD的dispatch

  利用dispatch_queue_create函数创建串行Dispatch Queue (serial dispatch queue)

  使用dispatch_get_global_queue函数获取全局并发Dispatch Queue (concurrent dispatch queue)

  使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue

  使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。

 

GCD常用方法:

1. dispatch_async

  为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
    // 耗时的操作  
    dispatch_async(dispatch_get_main_queue(), ^{  
        // 更新界面  
    });  
});  

2. dispatch_group_async

  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);  

dispatch_group_async是异步的方法,运行后可以看到打印结果:

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

每个一秒打印一个,当第三个任务执行后,upadteUi被打印。

 

3. dispatch_barrier_async

  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");  
});  

  打印结果:

2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1

2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2

2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async

2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

请注意执行的时间,可以看到执行的顺序如上所述。

 

4. dispatch_apply 

  执行某个代码片段N次。

dispatch_apply(5, globalQ, ^(size_t index) {
    // 执行5次
});

 

4、多线程在哪些情况下会使用/多线程使用场景?

  在网络请求,读取大量文件,操作数据库,大量数据解析,单例,延迟等一些耗时操作中,都会用到多线程。

 

5、三种多线程的优缺点?

  1. NSThread优点是比其他两种多线程方案较轻量级,更直观地控制线程对象。缺点是需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

  2. 项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
  3. 项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

 

以上是关于iOS 面试常问之多线程的主要内容,如果未能解决你的问题,请参考以下文章

面试常问之concurrentHashMap

大厂面试官:JDK 线程池中如何不超最大线程数快速消费任务?

如何确定线程池中线程数量

java面试之多线程

如何增加tomcat线程池中的线程数?

java 如何获得线程池中正在执行的线程数?