IOS开发者学习笔记iOS-面试

iOS-底层原理(21)-多线程面试题

2018-09-16  本文已影响24人  路飞_Luck
1.下面代码执行结果
- (void)viewDidLoad {
    [super viewDidLoad];
   // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
}

执行结果 - 会卡死

卡死.png

分析如下图

image.png

分析:同步执行 + 主队列会卡死当前线程,dispatch_sync不会开辟新的线程,在当前线程执行,并且立马在当前线程同步执行任务。主队列(也是一个串行队列)。主队列中有两个任务,分别是viewDidLoad和任务2。任务3执行完,才相当于viewDidLoad任务执行完毕,而任务3要等任务2执行完才能执行,但是任务2要等viewDidLoad执行完才能执行,所以造成相互等待。

结论:使用sync函数往当前串行队列中添加任务,会卡住当前串行队列(产生死锁

2.下面代码执行结果
- (void)test {
    NSLog(@"test");
}

/** 创建一个新线程执行 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");
        // 这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");
    });
}

执行结果

image.png
/** 任务在主线程执行 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"1");
    // 这句代码的本质是往Runloop中添加定时器
    [self performSelector:@selector(test) withObject:nil afterDelay:.0];
    NSLog(@"3");
}

执行结果

image.png
 /** 创建一个新线程执行,并且开启runloop */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{
        NSLog(@"1");
        // 这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];
        NSLog(@"3");

        // performSelector内部会将定时器添加到runloop中,runloop已经有定时器了,所以这里不需要再添加一个端口了
        // [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}

执行结果

image.png

分析
performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器,但是子线程默认没有启动Runloop
主线程默认开启了runloop

3 下面代码执行结果
/** 开启一个新线程,然后执行任务 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
    }];
    [thread start];
    
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

执行结果

image.png
/** 开启一个新线程,并且在线程中启动runloop,然后执行任务 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");

        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];

    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

执行结果

image.png

分析:因为创建一个线程,默认没有启动runloop,所以线程一启动执行完后就退出了。

  • 线程的任务一旦执行完毕,生命周期就结束,无法再使用
  • 保住线程的命为什么要使用runloop,用强指针不就可以了么?
  • 准确来说,使用runloop是为了让线程保持激活状态。
1.你理解的多线程?

Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

2.iOS的多线程方案有哪几种?你更倾向于哪一种?
image.png
3.你在项目中用过 GCD 吗?

必须有用到啊

4.GCD 的队列类型

GCD的队列可以分为2大类型

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

5.说一下 OperationQueue 和 GCD 的区别,以及各自的优势
6.线程安全的处理手段有哪些?

查看我的另外一篇文章 iOS-底层原理(23)-多线程的安全隐患+11种同步解决方案

7.OC你了解的锁有哪些?在你回答基础上进行二次提问;

查看我的另外一篇文章 iOS-底层原理(23)-多线程的安全隐患+11种同步解决方案

追问一:自旋和互斥对比?
追问二:使用以上锁需要注意哪些?
追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!

本文参考MJ底层原理教程,非常感谢


[项目源码 - 多线程面试题(https://github.com/chenshuangsmart/MultiThread/tree/master/%E5%A4%9A%E7%BA%BF%E7%A8%8B-%E9%9D%A2%E8%AF%95%E9%A2%98)

上一篇下一篇

猜你喜欢

热点阅读