iOS多線程相關面試題



iOS多線程demogit

iOS多線程之--NSThreadgithub

iOS多線程之--GCD詳解面試

iOS多線程之--NSOperation安全

iOS多線程之--線程安全(線程鎖)bash

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方法。app

咱們能夠手動啓動runloop來確保test1被調用,也就是在block裏面添加一行代碼[[NSRunLoop currentRunLoop] run];異步


若是把異步函數改成同步函數,咱們再來看下運行結果:async

- (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中,因此是按照一、二、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]);

// ***************打印結果(打印一、五、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

相關文章
相關標籤/搜索