GCD呼叫 引發的一些思考
阿新 • • 發佈:2018-11-11
前兩天同事,提了個有點意思的問題,今天突然想起來就整理一下,順便談一下自己的理解。
下面的這段程式碼會怎麼列印?
Dog * dog = [Dog new]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ if(dog.run){ dog.run(); NSLog(@"開始跑"); }else{ NSLog(@"跑不了"); } }); }); sleep(5); [dog setRun:^{ NSLog(@"可以跑"); }];
程式碼解釋:
-
第一步:建立一個 名為 dog的物件。
-
第二步:在GCD的延時函式中,非同步到主佇列判斷是否可以呼叫dog的run閉包,並進行相關處理。
-
第三步:呼叫sleep函式 讓執行緒暫停5秒。
-
第四步: dog的run閉包的實現,並在實現中列印訊息。
-
最後的輸出結果是:
為什麼會是這樣的輸出或者執行順序呢? 思考這個問題,我們不妨可以考慮一下幾種情況:
第一種:
延時函式,改為在main queue中呼叫。
第二種情況:
在延時函式的呼叫中,把非同步改為同步。
第三種情況:
我們把sleep函式的呼叫 註釋掉。
在以上三種的列印中,會是怎麼列印的?
其實列印的結果都是一樣的。
為什麼呢?
我們可以看一下 "簡化後"的程式碼:
上面的程式碼,我想應該是很容易理解的 首先執行非同步操作到主佇列中去提交任務,然後執行 dog 閉包的實現,最後的輸出結果是:
先列印: 可以跑
然後列印:開始跑。
這段程式碼的執行,我們可以這麼理解:
-
1、在主佇列中,執行非同步到主佇列 將 dog物件的閉包呼叫 提交到主佇列中去執行。
-
2、緊接著 dog物件的run閉包的實現。
執行的順序我們可以這麼理解:
如上圖所示:
也就是說首先執行到 dispatch_async這個函式, 因為它是非同步的 不會卡死當前的執行緒,所以main queue 或者說 主執行緒 會繼續往下執行,也就是會執行到 dog物件的實現。而非同步執行的任務是提交到 主佇列中的,我們知道主佇列是不具有併發執行的能力的。 所以此時,執行到 呼叫dog物件的run閉包,可以正常呼叫。
如此,最開始的問題,我們也就水到渠成可以理解了。
在上面提及的幾種情況,其執行的主要步驟機制便是如此,所以結果也是如此。
提出問題的情況中我們需要注意一下幾點:
-
1、雖然延時函式的呼叫並沒有什麼意義,但是我們需要注意延時函式執行的佇列,然後在延時函式的呼叫中,考慮使用 同步還是非同步,避免死鎖的問題。
-
2、我們常說的非同步執行或者同步執行 需要注意的是都是相對於當前的執行緒,但是具體會不會開闢新的執行緒去執行任務,還要看所處於的執行的佇列。
-
3、sleep函式是誰呼叫,誰就會去睡覺,而且sleep並不會讓出所使用的系統資源,除非強行interrupt.
-
4、看問題,我們要儘量去掉 一些天花亂墜的掩飾 (如上面的 延時呼叫、執行緒休眠等),只有直視問題本身,才會得到最真實的答案。