並發的意思就是同時運行多個任務,這些任務可以在單核CPU上以分時(時間共享)的形式同時運行,或者在多核CPU上以真正的並行來運行多任務。
在移動和桌面操作系統中,蘋果提供了相同的並發編程API。 NSThread、Grand Central Dispatch(GCD)、NSOperationQueue
線程(thread)是組成進程的子單元,操作系統的調度器可以對線程進行單獨的調度。實際上,所有的並發編程API都是構建於線程之上的——包括GCD和操作隊列(operation queues)。
多線程可以在單核CPU上同時運行(可以理解為同一時間)——操作系統將時間片分配給每一個線程,這樣就能夠讓用戶感覺到有多個任務在同時進行。如果CPU是多核的,那麼線程就可以真正的以並發方式被執行,所以完成某項操作,需要的總時間更少。
需要重點關注的一件事:開發者無法控制代碼在什麼地方以及什麼時候被調度,以及無法控制代碼執行多長時間後將被暫停,以便輪到執行別的任務。線程調度是非常強大的一種技術,但是也非常復雜(稍後會看到)。
1. NSThread
NSThread是Objective-C對pthread的一個封裝。通過封裝,在Cocoa環境中,可以讓代碼看起來更加親切。
在編程中,直接使用線程(無論是pthread,還是NSThread)都是難以接受的。
使用線程會引發的一個問題就是:在開發者自己的代碼,或者系統內部的框架代碼中,被激活的線程數量很有可能會成倍的增加——這對於一個大型工程來說,是很常見的。例如,在8核CPU中,你創建了8個線程,然後在這些線程中調用了框架代碼,這些代碼也創建了同樣的線程(其實它並不知道你已經創建好線程了),這樣會很快產生成千上萬個線程,最終導致你的程序被終止執行——線程實際上並不是免費的咖啡,每個線程的創建都會消耗一些內容,以及相關的內核資源。
2. Grand Central Dispatch
通過GCD,開發者不用再直接跟線程打交道了,只需要向隊列中添加block代碼即可,GCD在後端管理著一個線程池。GCD不僅決定著哪個線程(block)將被執行,它還根據可用的系統資源對線程池中的線程進行管理——這樣可以不通過開發者來集中管理線程,緩解大量線程的創建,做到了讓開發者遠離線程的管理。
默認情況下,GCD公開有5個不同的隊列:運行在主線程中的main queue,3個不同優先級的後台隊列,以及一個優先級更低的後台隊列(用於I/O)。另外,開發者可以創建自定義隊列:串行或者並行隊列。自定義隊列非常強大,在自定義隊列中被調度的所有block都將被放入到系統的線程池的一個全局隊列中。
這裡隊列中,可以使用不同優先級,這聽起來可能非常簡單,不過,強烈建議,在大多數情況下使用默認的優先級就可以了。在隊列中調度具有不同優先級的任務時,如果這些任務需要訪問一些共享的資源,可能會迅速引起不可預料到的行為,這樣可能會引起程序的突然停止——運行時,低優先級的任務阻塞了高優先級任務。
3. Operation Queues
操作隊列(operation queue)是基於GCD封裝的一個隊列模型。GCD提供了更加底層的控制,而操作隊列在GCD之上實現了一些方便的功能,這些功能對於開發者來說會更好、更安全。
類NSOperationQueue有兩個不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後台執行。任何情況下,在這兩種隊列中運行的任務,都是由NSOperation組成。
定義自己的操作有兩種方式:重寫main或者start方法,前一種方法非常簡單,但是靈活性不如後一種。對於重寫main方法來說,開發者不需要管理一些狀態屬性(例如isExecuting和isFinished)——當main返回的時候,就可以假定操作結束。
如果你希望擁有更多的控制權,以及在一個操作中可以執行異步任務,那麼就重寫start方法.
注意:這種情況下,需要開發者手動管理操作的狀態。 為了讓操作隊列能夠捕獲到操作的改變,需要將狀態屬性以KVO的方式實現。並確保狀態改變的時候發送了KVO消息。
從本質上來看,操作隊列的性能比GCD要低,不過,大多數情況下,可以忽略不計,所以操作隊列是並發編程的首選API。