iOS多线程相关面试题

iOS多线程demo

iOS多线程之--NSThread

iOS多线程之--GCD详解

iOS多线程之--NSOperation

iOS多线程之--线程安全(线程锁)

iOS多线程相关面试题

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

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 系列方法中只要是方法名中包含 afterDelaywaitUntilDone 的都是和计时器有关的,都要注意前面出现的这些问题。

2. 面试题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]];
    }];
复制代码

3. 面试题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 执行完了才会继续往下执行,这样就造成了相互等待而死锁。

4. 面试题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 。所以并不会造成死锁。

5. 面试题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 放入子线程中去执行,开启子线程是要耗时的,而且异步任务不需要等待就可以继续执行它后面的代码,所以打印 任务5block1 前面执行。

再来看 block1 任务块,先打印 任务2 ,然后通过同步函数添加的 block2 任务块需要立马执行,而 block1 所在的队列是串行队列, block1 任务块还没执行完,所以要先等 block1 执行,而 block1 又要等 block2 执行完了才能继续往下执行,所以就造成了相互等待而死锁。

6. 面试题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

7. 面试题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

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章