任務與佇列 iOS之多執行緒GCD(一)

小科科發表於2019-01-23

佇列

特點:先進先出的資料結構

分類:序列佇列 並行佇列 全域性並行佇列 主佇列

任務:就是block程式碼快要執行的程式碼 同步執行(sync) 非同步執行(async)

多執行緒 簡單理解就是除主執行緒外開闢了其他執行緒、增加執行效率。大白話就是程式碼有多條執行路徑。對於單核的iOS系統、多執行緒之間其實是併發的而不是多核的並行。也就是單核的iOS系統,各個執行緒在單位時間是來回切換的、造成了並行的假象。

GCD是基於c語言封裝的、在這裡只談談用法。用法就是建立佇列,將任務新增到佇列中執行。

如何建立佇列?

序列佇列
dispatch_queue_t queue1 = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
主佇列 特殊的序列佇列---所有主佇列的任務都會放在主執行緒執行
dispatch_queue_t mainQueue = dispatch_get_main_queue();
並行佇列
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
全域性並行佇列
dispatch_queue_t queueAll = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製程式碼

任務

任務分為同步執行、非同步執行

同步不具備開啟執行緒的能力
dispatch_sync(queue, ^{
    <#code#>
});
非同步具備開啟執行緒的能力、具體是否開啟了執行緒要結合佇列
dispatch_async(queue1, ^{
    <#code#>
});
複製程式碼

任務和佇列組合 page表格截圖如下:(由於全域性併發佇列也可以看作併發佇列、所以不予考慮)

任務與佇列  iOS之多執行緒GCD(一)

一、同步+序列佇列

特點:不開啟新的執行緒、任務按順序執行

- (void)syncSerial {
    NSLog(@"當前執行緒為:%@",[NSThread currentThread]);  // 當前執行緒
    NSLog(@"開始");
    //這裡建立序列佇列
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務三、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"結束");
}
複製程式碼

列印結果:

2019-01-23 09:49:41.098930+0800 多執行緒demo[922:34551] 當前執行緒為:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:41.099092+0800 多執行緒demo[922:34551] 開始
2019-01-23 09:49:43.100540+0800 多執行緒demo[922:34551] 我是任務一、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:45.101953+0800 多執行緒demo[922:34551] 我是任務一、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:47.103420+0800 多執行緒demo[922:34551] 我是任務二、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:49.104828+0800 多執行緒demo[922:34551] 我是任務二、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:51.106334+0800 多執行緒demo[922:34551] 我是任務三、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:53.106785+0800 多執行緒demo[922:34551] 我是任務三、來自執行緒:<NSThread: 0x604000073a80>{number = 1, name = main}
2019-01-23 09:49:53.106981+0800 多執行緒demo[922:34551] 結束

複製程式碼

二、同步+併發佇列

特點:不開啟新的執行緒、任務按順序執行

NSLog(@"當前執行緒%@",[NSThread currentThread]);  // 列印當前執行緒
    NSLog(@"開始");
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務三、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"結束");
複製程式碼

列印結果:

2019-01-23 10:02:31.505366+0800 多執行緒demo[1053:44225] 當前執行緒<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:31.505740+0800 多執行緒demo[1053:44225] 開始
2019-01-23 10:02:33.506119+0800 多執行緒demo[1053:44225] 我是任務一、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:35.507093+0800 多執行緒demo[1053:44225] 我是任務一、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:37.507964+0800 多執行緒demo[1053:44225] 我是任務二、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:39.508543+0800 多執行緒demo[1053:44225] 我是任務二、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:41.509973+0800 多執行緒demo[1053:44225] 我是任務三、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:43.511403+0800 多執行緒demo[1053:44225] 我是任務三、來自執行緒:<NSThread: 0x600000064d00>{number = 1, name = main}
2019-01-23 10:02:43.511608+0800 多執行緒demo[1053:44225] 結束
複製程式碼

三、非同步+序列佇列

特點:開啟了新的執行緒、任務順序執行

  NSLog(@"當前執行緒%@",[NSThread currentThread]);  // 列印當前執行緒
    NSLog(@"開始");
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務三、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"結束");
複製程式碼

列印結果:

2019-01-23 10:09:26.140699+0800 多執行緒demo[1116:48819] 當前執行緒<NSThread: 0x600000076040>{number = 1, name = main}
2019-01-23 10:09:26.140906+0800 多執行緒demo[1116:48819] 開始
2019-01-23 10:09:26.141049+0800 多執行緒demo[1116:48819] 結束
2019-01-23 10:09:28.143936+0800 多執行緒demo[1116:48854] 我是任務一、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
2019-01-23 10:09:30.148732+0800 多執行緒demo[1116:48854] 我是任務一、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
2019-01-23 10:09:32.154261+0800 多執行緒demo[1116:48854] 我是任務二、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
2019-01-23 10:09:34.156924+0800 多執行緒demo[1116:48854] 我是任務二、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
2019-01-23 10:09:36.158106+0800 多執行緒demo[1116:48854] 我是任務三、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
2019-01-23 10:09:38.162608+0800 多執行緒demo[1116:48854] 我是任務三、來自執行緒:<NSThread: 0x6000004629c0>{number = 3, name = (null)}
複製程式碼

四、非同步+併發佇列

特點:開啟新的執行緒、任務交替執行

    NSLog(@"當前執行緒%@",[NSThread currentThread]);  // 列印當前執行緒
    NSLog(@"開始");
    
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];            
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務三、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"結束");
複製程式碼

列印結果:

2019-01-23 10:15:29.424356+0800 多執行緒demo[1190:53683] 當前執行緒<NSThread: 0x604000074740>{number = 1, name = main}
2019-01-23 10:15:29.424575+0800 多執行緒demo[1190:53683] 開始
2019-01-23 10:15:29.424723+0800 多執行緒demo[1190:53683] 結束
2019-01-23 10:15:31.430195+0800 多執行緒demo[1190:53767] 我是任務三、來自執行緒:<NSThread: 0x60400047f5c0>{number = 5, name = (null)}
2019-01-23 10:15:31.430195+0800 多執行緒demo[1190:53764] 我是任務二、來自執行緒:<NSThread: 0x600000278a80>{number = 3, name = (null)}
2019-01-23 10:15:31.430195+0800 多執行緒demo[1190:53763] 我是任務一、來自執行緒:<NSThread: 0x60400047fe40>{number = 4, name = (null)}
2019-01-23 10:15:33.435111+0800 多執行緒demo[1190:53763] 我是任務一、來自執行緒:<NSThread: 0x60400047fe40>{number = 4, name = (null)}
2019-01-23 10:15:33.435111+0800 多執行緒demo[1190:53767] 我是任務三、來自執行緒:<NSThread: 0x60400047f5c0>{number = 5, name = (null)}
2019-01-23 10:15:33.435111+0800 多執行緒demo[1190:53764] 我是任務二、來自執行緒:<NSThread: 0x600000278a80>{number = 3, name = (null)}
複製程式碼

五、非同步+主佇列(任務執行 類似為同步+序列佇列)

特點:主執行緒執行(主佇列的任務都是在主執行緒)、任務按順序執行

    NSLog(@"當前執行緒為:%@",[NSThread currentThread]);  // 列印當前執行緒
    NSLog(@"開始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);      // 列印當前執行緒
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操作
            NSLog(@"我是任務三、來自執行緒:%@",[NSThread currentThread]);      // 列印當前執行緒
        }
    });
    
    NSLog(@"結束");
複製程式碼

列印結果:

2019-01-23 10:19:33.503411+0800 多執行緒demo[1242:56892] 當前執行緒為:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:33.503588+0800 多執行緒demo[1242:56892] 開始
2019-01-23 10:19:33.503736+0800 多執行緒demo[1242:56892] 結束
2019-01-23 10:19:35.538348+0800 多執行緒demo[1242:56892] 我是任務一、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:37.539726+0800 多執行緒demo[1242:56892] 我是任務一、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:39.541125+0800 多執行緒demo[1242:56892] 我是任務二、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:41.542511+0800 多執行緒demo[1242:56892] 我是任務二、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:43.543439+0800 多執行緒demo[1242:56892] 我是任務三、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
2019-01-23 10:19:45.544663+0800 多執行緒demo[1242:56892] 我是任務三、來自執行緒:<NSThread: 0x60000006eac0>{number = 1, name = main}
複製程式碼

六、同步+主佇列

特點:佇列引起的死鎖

- (void)syncMain {
    NSLog(@"當前執行緒:%@",[NSThread currentThread]);  // 列印當前執行緒
    NSLog(@"開始");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務一、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"我是任務二、來自執行緒:%@",[NSThread currentThread]);
        }
    });
    NSLog(@"結束");
}
複製程式碼

列印結果:

2019-01-23 10:25:00.640438+0800 多執行緒demo[1307:61022] 當前執行緒:<NSThread: 0x600000069a80>{number = 1, name = main}
2019-01-23 10:25:00.640607+0800 多執行緒demo[1307:61022] 開始
(lldb) 崩潰
複製程式碼

分析:由於主佇列是序列佇列、我們把syncMain方法看成一個任務、任務一看成一個任務。由於這兩個任務都在主佇列執行。主佇列是特殊的序列佇列。特點是任務按順序執行。所以先執行syncMain、而syncMain要等任務一執行完才能結束。任務一要等syncMain結束再執行。兩個任務互相等待。造成死鎖。這就是為什麼同步在主佇列執行會死鎖而同步在序列佇列不死鎖的原因。

相關文章