iOS多线程相关面试题

作者: iOS鑫 | 来源:发表于2021-03-12 14:04 被阅读0次

    首先要说明一下,下面所有面试题调用的方法(比如第一个面试题调用的方法是interview1)都是在主线程中调用的。

    作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130 595 548,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

    1. 面试题1
    - (void)interview1{
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        dispatch_async(queue, ^{
            NSLog(@"1---%@",[NSThread currentThread]);
            [self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
            NSLog(@"3---%@",[NSThread currentThread]);
        });
    }
    
    - (void)test1{
        NSLog(@"2---%@",[NSThread currentThread]);
    }
    
    // ***************打印结果***************
    2019-12-30 17:37:58.427558+0800 MultithreadingDemo[39113:4277962] 1---<NSThread: 0x600001922d40>{number = 6, name = (null)}
    2019-12-30 17:37:58.427659+0800 MultithreadingDemo[39113:4277962] 3---<NSThread: 0x600001922d40>{number = 6, name = (null)}
    
    

    解释: performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器(即使延时时间是0秒)。由于异步函数dispatch_async是开启一个新的子线程去执行任务,而子线程默认是没有启动Runloop的,所以并不会执行test1方法。
    我们可以手动启动runloop来确保test1被调用,也就是在block里面添加一行代码[[NSRunLoop currentRunLoop] run];。

    如果把异步函数改为同步函数,我们再来看下运行结果:

    - (void)interview1{
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        dispatch_sync(queue, ^{
            NSLog(@"1---%@",[NSThread currentThread]);
            [self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
            NSLog(@"3---%@",[NSThread currentThread]);
        });
    }
    
    - (void)test1{
        NSLog(@"2---%@",[NSThread currentThread]);
    }
    
    // ***************打印结果***************
    2019-12-30 17:47:01.936609+0800 MultithreadingDemo[39150:4282068] 1---<NSThread: 0x6000009660c0>{number = 1, name = main}
    2019-12-30 17:47:01.936724+0800 MultithreadingDemo[39150:4282068] 3---<NSThread: 0x6000009660c0>{number = 1, name = main}
    2019-12-30 17:47:01.936904+0800 MultithreadingDemo[39150:4282068] 2---<NSThread: 0x6000009660c0>{number = 1, name = main}
    

    解释: 同步函数添加的任务是在当前线程中执行,当前线程就是主线程,而主线程的Runloop是启动的,所以test1会调用。虽然延迟时间时0秒,但是添加到Runloop中的计时器不是立马触发的,而是要先唤醒Runloop,这是需要消耗一定时间的,所以会先打印3再打印2。

    我们再把performSelector:withObject:afterDelay:替换成performSelector:withObject:看看运行结果:

    - (void)interview1{
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        
        dispatch_async(queue, ^{
            NSLog(@"1---%@",[NSThread currentThread]);
            [self performSelector:@selector(test1) withObject:nil];
            NSLog(@"3---%@",[NSThread currentThread]);
        });
    }
    
    - (void)test1{
        NSLog(@"2---%@",[NSThread currentThread]);
    }
    
    // ***************打印结果***************
    2019-12-30 17:54:18.072035+0800 MultithreadingDemo[39183:4285659] 1---<NSThread: 0x60000303c300>{number = 3, name = (null)}
    2019-12-30 17:54:18.072136+0800 MultithreadingDemo[39183:4285659] 2---<NSThread: 0x60000303c300>{number = 3, name = (null)}
    2019-12-30 17:54:18.072215+0800 MultithreadingDemo[39183:4285659] 3---<NSThread: 0x60000303c300>{number = 3, name = (null)}
    

    解释: performSelector:withObject:函数是不涉及到计时器的,所以不会添加到Runloop中,所以是按照1、2、3的顺序执行。
    注意:performSelector系列方法中只要是方法名中包含afterDelay、waitUntilDone的都是和计时器有关的,都要注意前面出现的这些问题。

    1. 面试题2
    - (void)interview2{
        NSThread *thread = [[NSThread alloc] initWithBlock:^{
           NSLog(@"1---%@",[NSThread currentThread]);
        }];
        [thread start];
        
        [self performSelector:@selector(test2) onThread:thread withObject:nil waitUntilDone:YES];
    }
    
    - (void)test2{
        NSLog(@"2---%@",[NSThread currentThread]);
    }
    
    // ***************运行结果(闪退)***************
    2019-12-31 08:36:07.132133+0800 MultithreadingDemo[40268:4493885] 1---<NSThread: 0x6000010d9880>{number = 6, name = (null)}
    2019-12-31 08:36:07.432190+0800 MultithreadingDemo[40268:4493455] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[Interview performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
    

    解释: 从运行结果可以看出闪退的原因是target thread exited(目标线程退出)。因为test2方法是在线程thread上执行的,但是线程thread在执行完NSLog(@"1---%@",[NSThread currentThread]);这句代码后就结束了,所以等到执行test2方法时线程thread已经不存在了(严格来说是线程对象是还存在的,只是已经失活了,不能再执行任务了)。
    如果想要代码能正常运行,我们可以利用Runloop知识来保活线程。先向当前runloop中添加一个source(如果runloop中一个source、NSTime或Obserer都没有的话就会退出),然后启动runloop。也就是在线程thread的block中添加2行代码,如下所示:

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
           NSLog(@"1---%@",[NSThread currentThread]);
            
            // 线程保活
            // 先向当前runloop中添加一个source(如果runloop中一个source、NSTime或Obserer都没有的话就会退出)
            [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSRunLoopCommonModes];
            // 然后启动runloop
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }];
    
    1. 面试题3
    - (void)interview3{
        NSLog(@"执行任务1--%@",[NSThread currentThread]);
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"执行任务2--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务3--%@",[NSThread currentThread]);
    }
    

    运行结果:
    执行完任务1后就被卡死了。
    解释:
    interview3方法是在主线程中执行,执行完任务1后,通过同步函数向主队列(串行队列)添加任务2,由于同步添加的任务必须马上执行,而串行队列中当前任务(interview3)还没执行完,就没法安排任务2执行,所以要等当前正在执行的任务(interview3)执行完了后才能执行任务2,而interview3又要等任务2执行完了才会继续往下执行,这样就造成了相互等待而死锁。

    1. 面试题4
    - (void)interview4{
        NSLog(@"执行任务1--%@",[NSThread currentThread]);
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            NSLog(@"执行任务2--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务3--%@",[NSThread currentThread]);
    }
    
    // ***************打印结果***************
    2019-12-31 10:06:37.782135+0800 MultithreadingDemo[41281:4538099] 执行任务1--<NSThread: 0x600003cd1d80>{number = 1, name = main}
    2019-12-31 10:06:37.782244+0800 MultithreadingDemo[41281:4538099] 执行任务3--<NSThread: 0x600003cd1d80>{number = 1, name = main}
    2019-12-31 10:06:37.782574+0800 MultithreadingDemo[41281:4538099] 执行任务2--<NSThread: 0x600003cd1d80>{number = 1, name = main}
    

    解释:
    这和前面一个面试题相比只是把同步函数换成了异步函数。执行完任务1后,通过异步函数添加任务2,虽然异步函数有开启子线程的能力,但是由于是在主队列中,主队列的任务都是在主线程中执行,所以并不会开启子线程。由于是异步函数添加的任务2,所以不必等待任务2就可以继续往下执行,等当前任务(interview4)完成后串行队列再安排执行任务2。所以并不会造成死锁。

    1. 面试题5
    - (void)interview5{
        NSLog(@"执行任务1--%@",[NSThread currentThread]);
        
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            NSLog(@"执行任务2--%@",[NSThread currentThread]);
            
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3--%@",[NSThread currentThread]);
            });
        
            NSLog(@"执行任务4--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务5--%@",[NSThread currentThread]);
    
    // ***************打印结果(打印1、5、2后卡死)***************    
    2019-12-31 10:45:29.071774+0800 MultithreadingDemo[41379:4551961] 执行任务1--<NSThread: 0x6000038460c0>{number = 1, name = main}
    2019-12-31 10:45:29.071923+0800 MultithreadingDemo[41379:4551961] 执行任务5--<NSThread: 0x6000038460c0>{number = 1, name = main}
    2019-12-31 10:45:29.071932+0800 MultithreadingDemo[41379:4552048] 执行任务2--<NSThread: 0x600003824f40>{number = 6, name = (null)}
    }
    

    解释: 首先打印任务1,然后自己创建了一个串行队列,并通过异步函数向这个队列中添加一个任务块(block1),异步函数会开启一个子线程并将block1放入子线程中去执行,开启子线程是要耗时的,而且异步任务不需要等待就可以继续执行它后面的代码,所以打印任务5在block1前面执行。
    再来看block1任务块,先打印任务2,然后通过同步函数添加的block2任务块需要立马执行,而block1所在的队列是串行队列,block1任务块还没执行完,所以要先等block1执行,而block1又要等block2执行完了才能继续往下执行,所以就造成了相互等待而死锁。

    1. 面试题6
    - (void)interview6{
    
        NSLog(@"执行任务1--%@",[NSThread currentThread]);
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
        
        dispatch_async(queue, ^{
            NSLog(@"执行任务2--%@",[NSThread currentThread]);
            
            dispatch_sync(queue2, ^{
                NSLog(@"执行任务3--%@",[NSThread currentThread]);
            });
            
            NSLog(@"执行任务4--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务5--%@",[NSThread currentThread]);
    }
    
    // ***************打印结果*************** 
    2019-12-31 11:01:47.812260+0800 MultithreadingDemo[41405:4557566] 执行任务1--<NSThread: 0x600002836180>{number = 1, name = main}
    2019-12-31 11:01:47.812470+0800 MultithreadingDemo[41405:4557566] 执行任务5--<NSThread: 0x600002836180>{number = 1, name = main}
    2019-12-31 11:01:47.812488+0800 MultithreadingDemo[41405:4557684] 执行任务2--<NSThread: 0x600002830980>{number = 5, name = (null)}
    2019-12-31 11:01:47.812567+0800 MultithreadingDemo[41405:4557684] 执行任务3--<NSThread: 0x600002830980>{number = 5, name = (null)}
    2019-12-31 11:01:47.812648+0800 MultithreadingDemo[41405:4557684] 执行任务4--<NSThread: 0x600002830980>{number = 5, name = (null)}
    

    解释:
    这个和面试题5相比就是新加了一个队列(不管是串行队列还是并发队列都一样),block1任务块和block2任务块分别放在不同的队列中。
    先打印任务1再打印任务5和前面是一样的。然后异步函数会开启子线程去执行block1任务块,block1中先打印任务2,然后通过同步函数向另一个队列中添加block2任务块,由于两个block属于不同的队列,block2可以立马被安排执行而不会死锁,所以接着是打印任务3,最后打印任务4。

    1. 面试题7
    - (void)interview7{
        NSLog(@"执行任务1--%@",[NSThread currentThread]);
        
        dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            NSLog(@"执行任务2--%@",[NSThread currentThread]);
            
            dispatch_sync(queue, ^{
                NSLog(@"执行任务3--%@",[NSThread currentThread]);
            });
            
            NSLog(@"执行任务4--%@",[NSThread currentThread]);
        });
        
        NSLog(@"执行任务5--%@",[NSThread currentThread]);
    }
    
    // ***************打印结果***************
    2019-12-31 11:14:27.690008+0800 MultithreadingDemo[41445:4562142] 执行任务1--<NSThread: 0x6000011badc0>{number = 1, name = main}
    2019-12-31 11:14:27.690102+0800 MultithreadingDemo[41445:4562142] 执行任务5--<NSThread: 0x6000011badc0>{number = 1, name = main}
    2019-12-31 11:14:27.690122+0800 MultithreadingDemo[41445:4562301] 执行任务2--<NSThread: 0x6000011f5900>{number = 3, name = (null)}
    2019-12-31 11:14:27.690202+0800 MultithreadingDemo[41445:4562301] 执行任务3--<NSThread: 0x6000011f5900>{number = 3, name = (null)}
    2019-12-31 11:14:27.690285+0800 MultithreadingDemo[41445:4562301] 执行任务4--<NSThread: 0x6000011f5900>{number = 3, name = (null)}
    

    解释:
    这个和面试题5相比是把串行队列换成了并发队列。
    先打印任务1再打印任务5和前面是一样的。然后异步函数会开启子线程去执行block1任务块,block1中先打印任务2,然后通过同步函数向并发队列中添加block2任务块,并发队列不需要等前一个任务完成就可以安排下一个任务执行,所以block2可以立马执行打印任务3,最后再打印任务4。

    作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是大牛还是小白都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)

    相关文章

      网友评论

        本文标题:iOS多线程相关面试题

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