GCD
GCD中兩個非常重要的概念: 任務 和 佇列
任務分為同步執行sync和非同步執行async, 同步和非同步的區別在於是否會阻塞當前執行緒, 其實在GCD中一個任務就是一個block中的程式碼.
佇列分為序列佇列和並行佇列,主佇列dispatch_get_main_queue( )是序列佇列.
我們可以使用dispatch_queue_create來建立新的佇列,
//序列佇列
dispatch_queue_t queue = dispatch_queue_create("testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
//並行佇列
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
下面是我自己總結的:
/ | 同步執行sync | 非同步執行async |
---|---|---|
序列佇列 | 當前執行緒,一個一個執行 | 其他一個執行緒, 一個一個執行 |
並行佇列 | 當前執行緒,一個一個執行 | 其他一個或者多個執行緒(取決於任務數), 同時執行 |
通過程式碼來驗證一下:
-
建立一個序列的佇列新增4個同步執行的任務
//序列佇列 dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"111"); NSLog(@"111中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"222"); NSLog(@"222中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"333"); NSLog(@"333中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"444"); NSLog(@"444中 %@",[NSThread currentThread]); });
列印結果:
2018-03-09 15:34:26.474786+0800 Learning[3385:281473] 111 2018-03-09 15:34:26.475053+0800 Learning[3385:281473] 111中 <NSThread: 0x60000007a2c0>{number = 1, name = main} 2018-03-09 15:34:26.475165+0800 Learning[3385:281473] 222 2018-03-09 15:34:26.475369+0800 Learning[3385:281473] 222中 <NSThread: 0x60000007a2c0>{number = 1, name = main} 2018-03-09 15:34:26.475568+0800 Learning[3385:281473] 333 2018-03-09 15:34:26.476057+0800 Learning[3385:281473] 333中 <NSThread: 0x60000007a2c0>{number = 1, name = main} 2018-03-09 15:34:26.476249+0800 Learning[3385:281473] 444 2018-03-09 15:34:26.476351+0800 Learning[3385:281473] 444中 <NSThread: 0x60000007a2c0>{number = 1, name = main}
-
同步dispatch_sync改為非同步dispatch_async
列印結果依然是有序, 但是開啟了一個子執行緒2018-03-09 16:23:47.634329+0800 Learning[3830:331376] 111 2018-03-09 16:23:47.634739+0800 Learning[3830:331376] 111中 <NSThread: 0x604000274dc0>{number = 4, name = (null)} 2018-03-09 16:23:47.634911+0800 Learning[3830:331376] 222 2018-03-09 16:23:47.635262+0800 Learning[3830:331376] 222中 <NSThread: 0x604000274dc0>{number = 4, name = (null)} 2018-03-09 16:23:47.636508+0800 Learning[3830:331376] 333 2018-03-09 16:23:47.637206+0800 Learning[3830:331376] 333中 <NSThread: 0x604000274dc0>{number = 4, name = (null)} 2018-03-09 16:23:47.637413+0800 Learning[3830:331376] 444 2018-03-09 16:23:47.637680+0800 Learning[3830:331376] 444中 <NSThread: 0x604000274dc0>{number = 4, name = (null)}
-
如果把新建的佇列改為並行佇列, 同步執行
//並行佇列 dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ NSLog(@"111"); NSLog(@"111中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"222"); NSLog(@"222中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"333"); NSLog(@"333中 %@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"444"); NSLog(@"444中 %@",[NSThread currentThread]); });
結果
2018-03-09 16:31:50.498599+0800 Learning[3938:341075] 111 2018-03-09 16:31:50.498836+0800 Learning[3938:341075] 111中 <NSThread: 0x600000063d80>{number = 1, name = main} 2018-03-09 16:31:50.498946+0800 Learning[3938:341075] 222 2018-03-09 16:31:50.499101+0800 Learning[3938:341075] 222中 <NSThread: 0x600000063d80>{number = 1, name = main} 2018-03-09 16:31:50.499227+0800 Learning[3938:341075] 333 2018-03-09 16:31:50.499353+0800 Learning[3938:341075] 333中 <NSThread: 0x600000063d80>{number = 1, name = main} 2018-03-09 16:31:50.499461+0800 Learning[3938:341075] 444 2018-03-09 16:31:50.499609+0800 Learning[3938:341075] 444中 <NSThread: 0x600000063d80>{number = 1, name = main}
-
把上面程式碼中的同步改為非同步, 即並行佇列, 非同步執行
2018-03-09 16:34:13.089611+0800 Learning[3982:344351] 222 2018-03-09 16:34:13.089612+0800 Learning[3982:344350] 333 2018-03-09 16:34:13.089612+0800 Learning[3982:344349] 111 2018-03-09 16:34:13.089639+0800 Learning[3982:344352] 444 2018-03-09 16:34:13.089997+0800 Learning[3982:344350] 333中 <NSThread: 0x60000027d680>{number = 3, name = (null)} 2018-03-09 16:34:13.089997+0800 Learning[3982:344349] 111中 <NSThread: 0x604000271800>{number = 4, name = (null)} 2018-03-09 16:34:13.090004+0800 Learning[3982:344352] 444中 <NSThread: 0x604000271440>{number = 6, name = (null)} 2018-03-09 16:34:13.090031+0800 Learning[3982:344351] 222中 <NSThread: 0x604000271480>{number = 5, name = (null)}
從結果中可以看得出, 開啟了四個不同的現成來執行四個任務.
注意
在同步+序列的時候會有一個特殊的情況, 上面也提到了, 主佇列也是一個序列佇列, 如果當前在主執行緒中且把任務加到主佇列中會如何呢 ?
NSLog(@"---前 %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{//dispatch_sync
NSLog(@"---中 %@",[NSThread currentThread]);
});
NSLog(@"---後 %@",[NSThread currentThread]);
執行結果形成死鎖.
思考: 同樣都是序列佇列, 為什麼任務新增到主佇列會造成死鎖現象, 新建一個序列佇列則可以正常執行 ?
解釋: 在執行完第一個NSLog的時候, 遇到dispatch_sync, 會暫時阻塞主執行緒. 那麼我們想一下, 在阻塞之前, 主執行緒正在執行的是主佇列中的哪一個任務呢? 用我們實際的例子來解釋, 我們再寫上面的程式碼的時候, 這部分的程式碼是包裹在一個另方法中的, 就像我自己寫的demo, 這段程式碼就寫在viewDidLoad裡面, 因為viewDidLoad這些任務也是新增在主佇列中的, 所以說阻塞主執行緒時, 主執行緒應該是處理的主隊中 viewDidLoad這個任務.
我們呼叫了dispatch_sync後向主佇列中新增了新的任務, 也就是dispatch_sync後面block中的程式碼. 但是根據佇列的FIFO規則, 新新增的block中的任務, 肯定是要排在主佇列的最後.
因為是使用的dispatch_sync同步, 所以說必須要等執行dispatch_sync的block中的程式碼才會返回, 才會繼續執行ViewDidLoad中dispatch_sync這個方法下面的方法, 也就是程式碼中的第三個NSLog, 但是block中的任務被放在了主佇列的底部, 他是不可能在viewDidLoad這個任務還沒完成的時候就執行到的, 所以就形成了兩個任務互相等待的情況, 也就是形成了死鎖.
所以說這樣看來,GCD形成死鎖的原因應該是是佇列阻塞,而不是執行緒阻塞.
dispatch_group
解決先執行A和B, AB都執行完再執行C的這種情況.
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_group_async(group, queue, ^{
// 1
sleep(5);
NSLog(@"task 1");
});
dispatch_group_async(group, queue, ^{
//2
NSLog(@"task 2");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done ");
});
或者
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"11");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(7);
NSLog(@"22");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"33");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
結果都是先執行完上面的在列印這個done, 這個不需要解釋了.
有時候可能會碰到dispatch_group_wait, 注意一下, 因為他會阻塞執行緒, 所以不能放在主執行緒裡面執行, 等待的時間是以group中任務開始執行的時間算起.
dispatch_barrier_async
多執行緒最常見的問題就是讀寫,比如資料庫讀寫,檔案讀寫,讀取是共享的,寫是互斥.
使用Concurrent Dispatch Queue和dispatch_barrier_basync函式可實現高效率的資料庫訪問和檔案訪問。
如果允許多個執行緒進行讀操作,當寫檔案時,阻止佇列中所有其他的執行緒進入,直到檔案寫完成.
在讀取檔案時, 直接使用dispatch_async非同步讀取, 當要寫檔案是, 使用dispatch_barrier_async. 用程式碼看一下dispatch_async和dispatch_barrier_async的執行順序.
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"task 1");
});
dispatch_async(queue, ^{
sleep(2);
NSLog(@"task 2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier -- ");
sleep(3);
});
dispatch_async(queue, ^{
NSLog(@"task 3");
});
dispatch_async(queue, ^{
NSLog(@"task 4");
});
NSLog(@" end ");
結果是先列印完task1和2, 在列印完barrier, 最後列印task3和4, 同理到檔案讀寫中, 檔案讀取直接用dispatch_async, 寫入使用dispatch_barrier_async, 則在寫入檔案時, 不管前面有多少操作都會等待前面的操作完成, 而且在寫入的時候, 也沒有其他執行緒訪問, 從而達到執行緒安全.