libdispatch(or GCD)是最容易被使用錯誤的API之一,這是因為它的用法以及令人難懂的文檔和API。如果你打算使用這個庫,下面是總結是你要知道的。本文末尾有很多參考資料,包庫對蘋果自己的libdispatch維護人員(Pierre Habouzit)的評論的引用。
具體的結論如下:
- 應該創建適量、常駐、明確職能的隊列。這些隊列供應用程序執行上下文及其它并行任務(UI線程、后臺線程等)。需要注意的是,如果這些隊列處于活動狀態,意味著將有等量的線程數運行。在大部分的App中,只需要不超過3或者4個隊列即可。
- 優先使用串行隊列,如果使用過程中發現性能瓶頸,可以先查找原因。即使并行能提供幫助,也需謹慎使用,并且始終在系統壓力下驗證。盡可能的重用隊列,除非增加隊列能帶來明顯的收益。
- 你可以創建比較多非全局行的隊列(重點是他們有不同的標識),如使用
DispatchQueue(label:target:)
方式創建的隊列。 - 不要使用
DispatchQueue.global()
的方式獲取隊列。全局隊列容易導致線程爆炸:libdispatch
將阻塞、睡眠、等待、鎖定的線程視為非活動線程,從而在需要程序調度時又會創建新的線程。注意,我們不能保證線程永遠不會被阻塞,事實是,即使我們僅僅使用系統庫都可能會發生這種問題。全局隊列對qos
和priorities
的支持也不友好。蘋果工程師,libdispatch
的維護者宣稱它是"它提供了最糟糕的dispatch API"。用自定義的隊列代替它運行你的代碼。 - 并發隊列相對串行隊列不是最佳實踐。除非衡量了確切的收益,否則直接使用并發隊列更像是過早優化。
- 對于派發的小于1ms的任務,使用
queue.async()
的方式是非常糟糕的?;?code>libdispatch的過渡分配行為,它極有可能會創建一個新的線程。寧愿使用鎖定來保護共享狀態(而不是切換執行上下文)。 - 有些類/庫設計為同步API,重用調用者的執行上下文(而不是創建私有隊列,這可能導致糟糕的性能)這意味著得使用傳統的鎖來保證線程安全。
-
os_unfair_lock
是目前系統中最快的鎖(優先級更好,上下文切換更少),用于取代OSSpinLock
(取代原因可參考《不再安全的 OSSpinLock》),嘗試獲取已加鎖的線程無需忙等,解鎖時由內核喚醒。和OSSpinLock
一樣,os_unfair_lock
也沒有加強公平性和順序,它在Swift中直接使用卻是不安全的(參考StackOverFlow討論)。事實證明,pthread_mutex
的底層實現已被更新為os_unfair_lock
,而NSLock
本身是基于pthread_mutex
實現的,因此NSLock
是在Swift中鎖定的最佳選擇。 - 在調度任務派發后,不要阻塞當前正在等待信號量的線程或者調度組。因為內核不知道哪個線程最終會解除線程阻塞,從而導致執行低效。
- 不要在非GUI程序和庫中使用
DispatchQueue.main
,<dispatch/queue.h>
頭文件中的解釋如下:“ "Because the main queue doesn't behave entirely like a regular serial queue, it may have unwanted side-effects when used in processes that are not UI apps (daemons). For such processes, the main queue should be avoided"。是說主隊列并和普通的串行隊列不太一樣,如果在非UI操作的情況下調用,會導致一些意外情況。具體是啥意外,我還暫時舉不出例子。 - 如果要并行執行,工作項中最好沒有資源的競爭,否則性能會急劇下降,競爭有多種形式。加鎖來保證線程的安全,但也意味著被競爭的共享資源會成為性能的瓶頸: IPC/daemons, malloc (lock), shared memory, I/O等。
- 不要一直使用
async
,這樣可以避免線程爆炸。使用少量調度隊列來代替DispatchQueue.global()
是一個更好的解決辦法。 - 大量異步/回調設計的復雜性(和缺陷)也不容忽視。同步代碼仍然更易于閱讀、編寫和維護。