美文网首页
iOS多线程应用--面试

iOS多线程应用--面试

作者: 旅行者_sz | 来源:发表于2020-06-28 11:17 被阅读0次

问题:什么是线程?它与进程有什么区别?为什么要使用多线程?

线程是指程序在执行过程中,能够执行程序代码的一个执行单元。线程主要有四种状态:运行、就绪、挂起、结束。

进程是指一段正在执行的程序。而线程有时候也被称为轻量级进程,是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程拥有自己的栈空间,进程与线程的关系如下图所示:

image

在操作系统级别上,程序的执行都是以进程为单位的,而每个进程中通常都会有多个线程互不影响地并发执行,那么为什么要使用多线程呢?其实,多线程的使用为程序研发带来了巨大的便利,具体而言,有以下几个方面的内容:

使用多线程可以减少程序的响应时间。在单线程(单线程指的是程序执行过程中只有一个有效操作的序列,不同操作之间都有明确的执行先后顺序)的情况下,如果某个操作很耗时,或者陷入长时间的等待(如等待网络响应),此时程序将不会响应鼠标和键盘等操作,使用多线程后,可以把这个耗时的线程分配到一个单独的线程去执行,使得程序具备了更好的交互性。
与进程相比,线程的创建和切换开销更小。由于启动一个新的线程必须给这个线程分配独立的地址空间,建立许多数据结构来维护线程代码段、数据段等信息,而运行于同一进程内的线程共享代码段、数据段,线程的启动或切换的开销比进程要少很多。同时多线程在数据共享方面效率非常高。
多CPU或多核计算机本身就具有执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,造成资源的巨大浪费。因此在多CPU计算机上使用多线程能提高CPU的利用率。
使用多线程能简化程序的结构,使程序便于理解和维护。一个非常复杂的进程可以分成多个线程来执行。

问题: 列举Cocoa中常见的几种多线程的实现,并谈谈多线程安全的几种解决办法,一般什么地方会用到多线程?

问的是三个层次的多线程编程实现;线程锁的使用;

只在主线程刷新访问UI
如果要防止资源抢夺,得用synchronized进行加锁保护
如果异步操作要保证线程安全等问题, 尽量使用GCD(有些函数默认 就是安全的)

问题: OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? ***

问题: 在iphone上有两件事情要做,请问是在一个线程里按顺序做效率高还是两个线程里做效率高?为什么?

这里的效率高指的是时间上效率高,也就是希望在最短的时间内完成所有任务,两件事情按顺序做意味着串行执行,第二件事情要等待第一件事情结束后才可开始,效率相对很低。但如果利用两个线程让两件事情能够并发执行,则时间上效率会大大提高。 ***

问题: runloop是什么?在主线程中的某个函数里调用了异步函数,怎样block当前线程,且还能响应当前线程的timer事件,touch事件等? ***

问题: 对比在OS X和iOS中实现并发性的不同方法。

在iOS中有三种方法可以实现并发性:threads、dispatch queues和operation queues。

threads的缺点是开发者要自己创建合适的并发解决方案,要决定创建多少线程并且要根据情况动态调整线程的数量。并且应用还要为创建和维护线程承担主要代价, 因此OS X和IOS系统并不是依靠线程来解决并发问题的,而是采用了异步设计的途径。

其中一个开启异步任务的技术是GCD(Grand Central Dispatch),让系统层次来对线程进行管理。开发者要做的就是定义要执行的任务并将之添加到合适的dispatch分派队列。GCD会负责创建需要的线程并安排任务在这些线程上运行。所有的dispatch队列都是先进先出(FIFO)队列结构,所以任务的执行顺序是和添加顺序是一致的。

operation queues和并发dispatch队列一样都是通过Cocoa框架的NSOperationQueue类实现的,不过它并不一定是按照先进先出的顺序执行任务的,而是支持创建更复杂的执行顺序图来管理任务的执行顺序。 ***

问题: 操作队列(NSOperation queue)是什么?解释NSOperationQueue串行操作队列和并行操作队列的区别。 ***

问题: 用户下载一个图片,图片很大,需要分成很多份进行下载,使用GCD应该如何实现?使用什么队列?

使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

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, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并图片 });

问题:Dispatch_barrier_(a)sync的作用?
通过Dispatch_barrier_async添加的操作会暂时阻塞当前队列,即等待前面的并发操作都完成后执行该阻塞操作,待其完成后后面的并发操作才可继续。可以将其比喻为一座霸道的独木桥,是并发队列中的一个并发障碍点,临时阻塞并独占.

可见使用Dispatch_barrier_async可以实现类似dispatch_group_t组调度的效果,同时主要的作用是避免数据竞争,高效访问数据。

/* 创建并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
/* 添加两个并发操作A和B,即A和B会并发执行 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationA");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationB");
});
/* 添加barrier障碍操作,会等待前面的并发操作结束,并暂时阻塞后面的并发操作直到其完成 */
dispatch_barrier_async(concurrentQueue, ^(){
    NSLog(@"OperationBarrier!");
});
/* 继续添加并发操作C和D,要等待barrier障碍操作结束才能开始 */
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationC");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"OperationD");
});
2017-04-04 12:25:02.344 SingleView[12818:3694480] OperationB
2017-04-04 12:25:02.344 SingleView[12818:3694482] OperationA
2017-04-04 12:25:02.345 SingleView[12818:3694482] OperationBarrier!
2017-04-04 12:25:02.345 SingleView[12818:3694482] OperationD
2017-04-04 12:25:02.345 SingleView[12818:3694480] OperationC

问题: 用过NSOperationQueue吗?如果用过或者了解的话,为什么要使用 NSOperationQueue?实现了什么?简述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来述)。

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

GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
GCD只支持FIFO的队列,NSOperationQueue可以很方便地调整执行顺 序、设置最大并发数量
NSOperationQueue可以在轻松在Operation间设置依赖关系,而GCD 需要写很多的代码才能实现
NSOperationQueue支持KVO,可以监测operation是否正在执行 (isExecuted)、是否结束(isFinished),是否取消(isCanceld) 5> GCD的执行速度比NSOperationQueue快 任务之间不太互相依赖:GCD 任务之间有依赖\或者要监听任务的执行情况:NSOperationQueue

问题: 在使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?

使用block是要注意,若将block做函数参数时,需要把它放到最后,GCD是Grand Central Dispatch,是一个对线程开源类库,而Block是闭包,是能够读取其他函数内部变量的函数。

问题: 在项目什么时候选择使用GCD,什么时候选择 NSOperation?

项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现多线程支持,而接口简单,建议在复杂项目中使用。

项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

问题:对于a,b,c三个线程,如何使用用NSOpertion和NSOpertionQueue实现执行完a,b后再执行c?

可以通过NSOpertion的依赖特性实现该需求,即让c依赖于a和b,这样只有a和b都执行完后,c才可以开始执行:

/* 获取主队列(主线程) */
NSOperationQueue *queue = [NSOperationQueue mainQueue];
/* 创建a、b、c操作 */
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation C Start!");
    // ... ...
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation A Start!");
    [NSThread sleepForTimeInterval:3.0];
    NSLog(@"Operation A Done!");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Operation B Start!");
    [NSThread sleepForTimeInterval:3.0];
    NSLog(@"Operation B Done!");
}];
/* 添加操作依赖,c依赖于a和b */
[c addDependency:a];
[c addDependency:b];
/* 添加操作a、b、c到操作队列queue(特意将c在a和b之前添加) */
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];

2017-03-18 13:51:37.770 SingleView[15073:531745] Operation A Start!
2017-03-18 13:51:40.772 SingleView[15073:531745] Operation A Done!
2017-03-18 13:51:40.775 SingleView[15073:531745] Operation B Start!
2017-03-18 13:51:43.799 SingleView[15073:531745] Operation B Done!
2017-03-18 13:51:43.800 SingleView[15073:531745] Operation C Start!

问题:GCD内部是怎么实现的?

iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的
GCD的API全部在libdispatch库中
GCD的底层实现主要有Dispatch Queue和Dispatch Source
Dispatch Queue :管理block(操作)
Dispatch Source :处理事件

问题:既然提到GCD,那么问一下在使用GCD以及 block 时要注意些什么?它们两是一回事儿么? block 在 ARC 中和传统的 MRC 中的行为和用法有 没有什么区别,需要注意些什么?

Block的使用注意:

block的内存管理
防止循环retian
非ARC(MRC):__block
ARC:__weak__unsafe_unretained

问题:下面代码有什么问题?

int main(int argc, const char * argv[]) {
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
    return 0;
}

main函数中第二句代码在主线程上使用了dispatch_sync同步向主线程派发任务,而同步派发要等到任务完成后才能返回,阻塞当前线程。也就是说执行到此处,主线程被阻塞,同时又要等主线程执行完成该任务,造成主线程自身的等待循环,也就是死锁。程序运行到此处会崩溃。将dispatch_sync改为dispatch_async异步派发任务即可避免死锁,或者将任务派发到其他队列上而不是主队列。

问题: 简单介绍下NSURLConnection类,以及+sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别?

NSURLConnection主要用于网络链接请求,提供了异步和同步两种请求方式,异步请求会新创建一个线程单独用于之后的下载,不会阻塞当前调用的线程;同步请求会阻塞当前调用线程,等待它下载结束,如果在主线程上进行同步请求则会阻塞主线程,造成界面卡顿。

+sendSynchronousRequest:returningResponse:error:是同步请求数据,会阻塞当前的线程,直到request返回response;

–initWithRequest:delegate:是异步请求数据,不会足阻塞当前线程,当数据请求结束后会通过代理回到主线程,并通知它委托的对象。

问题: UIKit类要在哪一个应用线程上使用?

UIKit的界面类只能在主线程上使用,对界面进行更新,多线程环境中要对界面进行更新必须要切换到主线程上。 ***

问题: 以下代码有什么问题?如何修复?

@interface TTWaitController : UIViewController

@property (strong, nonatomic) UILabel *alert;

@end

@implementation TTWaitController

- (void)viewDidLoad
{
    CGRect frame = CGRectMake(20, 200, 200, 20);
    self.alert = [[UILabel alloc] initWithFrame:frame];
    self.alert.text = @"Please wait 10 seconds...";
    self.alert.textColor = [UIColor whiteColor];
    [self.view addSubview:self.alert];

    NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
    [waitQueue addOperationWithBlock:^{
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
        self.alert.text = @"Thanks!";
    }];
}

@end

@implementation TTAppDelegate

- (BOOL)application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.rootViewController = [[TTWaitController alloc] init];
    [self.window makeKeyAndVisible];
    return YES;
}

这段代码是想提醒用户等待10s,10s后在标签上显示“Thanks”,但多线程代码部分NSOperationQueue的addOperationWithBlock函数不能保证block里面的语句是在主线程中运行的,UILabel显示文字属于UI更新,必须要在主线程进行,否则会有未知的操作,无法在界面上及时正常显示。

解决方法是将UI更新的代码写在主线程上即可,代码同步到主线程上主要有三种方法:NSThread、NSOperationQueue和GCD,三个层次的多线程都可以获取主线程并同步。

NSThread级主线程同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    // 同步到主线程     [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
}];

/** * UI更新函数 */
- (void)updateUI {
    self.alert.text = @"Thanks!";
}

NSOperationQueue级主线程同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    // 同步到主线程     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.alert.text = @"Thanks!";
    }];
}];

GCD级主线程同步:

NSOperationQueue *waitQueue = [[NSOperationQueue alloc] init];
[waitQueue addOperationWithBlock:^{
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    // 同步到主线程     dispatch_async(dispatch_get_main_queue(), ^{
        self.alert.text = @"Thanks!";
    });
}];

相关文章

网友评论

      本文标题:iOS多线程应用--面试

      本文链接:https://www.haomeiwen.com/subject/vjkofktx.html