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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342