什麼是多執行緒
多執行緒是一個比較輕量級的方法來實現單個應用程式內多個程式碼執行路徑。從技術角度來看,一個執行緒就是一個需要管理執行程式碼的核心級和應用級資料結構組合。核心級結構協助排程執行緒事件,並搶佔式排程一個執行緒到可用的核心之上。應用級結構包括用於儲存函式呼叫的呼叫堆疊和應用程式需要管理和操作執行緒屬性和狀態的結構。
多執行緒的替代方法
你自己建立多執行緒程式碼的一個問題就是它會給你的程式碼帶來不確定性。多執行緒是一個相對較低的水平和複雜的方式來支援你的應用程式併發。如果你不完全理解你的設計選擇的影響,你可能很容易遇到同步或定時問題,其範圍可以從細微的行為變化到嚴重到讓你的應用程式崩潰並破壞使用者資料。
你需要考慮的另一個因素是你是否真的需要多執行緒或併發。多執行緒解決了如何在同一個程式內併發的執行多路程式碼路徑的問題。然而在很多情況下你是無法保證你所在做的工作是併發的。多執行緒引入帶來大量的開銷,包括記憶體消耗和CPU佔用。你會發現這些開銷對於你的工作而言實在太大,或者有其他方法會更容易實現。
1、Operation objects
Introduced in Mac OS X v10.5, an operation object is a wrapper for a task that would normally be executed on a secondary thread. This wrapper hides the thread management aspects of performing the task, leaving you free to focus on the task itself. You typically use these objects in conjunction with an operation queue object, which actually manages the execution of the operation objects on one more threads.
For more information on how to use operation objects, see Concurrency Programming Guide.
2、Grand Central Dispatch (GCD)
Introduced in Mac OS x v10.6, Grand Central Dispatch is another alternative to threads that lets you focus on the tasks you need to perform rather than on thread management. With GCD, you define the task you want to perform and add it to a work queue, which handles the scheduling of your task on an appropriate thread. Work queues take into account the number of available cores and the current load to execute your tasks more efficiently than you could do yourself using threads.
For information on how to use GCD and work queues, see Concurrency Programming Guide
3、Idle-time notifications
For tasks that are relatively short and very low priority, idle time notifications let you perform the task at a time when your application is not as busy. Cocoa provides support for idle-time notifications using the NSNotificationQueue object. To request an idle-time notification, post a notification to the default NSNotificationQueue object using the NSPostWhenIdle option. The queue delays the delivery of your notification object until the run loop becomes idle. For more information, see Notification Programming Topics.
4、Asynchronous functions
The system interfaces include many asynchronous functions that provide automatic concurrency for you. These APIs may use system daemons and processes or create custom threads to perform their task and return the results to you. (The actual implementation is irrelevant because it is separated from your code.) As you design your application, look for functions that offer asynchronous behavior and consider using them instead of using the equivalent synchronous function on a custom thread.
5、Timers
You can use timers on your application’s main thread to perform periodic tasks that are too trivial to require a thread, but which still require servicing at regular intervals. For information on timers, see “Timer Sources.”
6、Separate processes
Although more heavyweight than threads, creating a separate process might be useful in cases where the task is only tangentially related to your application. You might use a process if a task requires a significant amount of memory or must be executed using root privileges. For example, you might use a 64-bit server process to compute a large data set while your 32-bit application displays the results to the user.
執行緒支援
在應用層上,其他平臺一樣所有執行緒的行為本質上是相同的。執行緒啟動之後,執行緒就進入三個狀態中的任何一個:執行(running)、就緒(ready)、阻塞(blocked)。如果一個執行緒當前沒有執行,那麼它不是處於阻塞,就是等待外部輸入,或者已經準備就緒等待分配CPU。執行緒持續在這三個狀態之間切換,直到它最終退出或者進入中斷狀態。
1、Cocoa threads
Cocoa implements threads using the NSThread class. Cocoa also provides methods onNSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.”
2、POSIX threads
POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads”
3、Multiprocessing Services
Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in Mac OS X only and should be avoided for any new development. Instead, you should use the NSThread class or POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide.
同步工具
執行緒程式設計的危害之一是在多個執行緒之間的資源爭奪。如果多個執行緒在同一個時間試圖使用或者修改同一個資源,就會出現問題。緩解該問題的方法之一是消除共享資源,並確保每個執行緒都有在它操作的資源上面的獨特設定。因為保持完全獨立的資源是不可行的,所以你可能必須使用鎖,條件,原子操作和其他技術來同步資源的訪問。
鎖提供了一次只有一個執行緒可以執行程式碼的有效保護形式。最普遍的一種鎖是互斥排他鎖,也就是我們通常所說的“mutex”。當一個執行緒試圖獲取一個當前已經被其他執行緒佔據的互斥鎖的時候,它就會被阻塞直到其他執行緒釋放該互斥鎖。系統的幾個框架提供了對互斥鎖的支援,雖然它們都是基於相同的底層技術。此外Cocoa提供了幾個互斥鎖的變種來支援不同的行為型別,比如遞迴。
除了鎖,系統還提供了條件,確保在你的應用程式任務執行的適當順序。一個條件作為一個看門人,阻塞給定的執行緒,直到它代表的條件變為真。當發生這種情況的時候,條件釋放該執行緒並允許它繼續執行。POSIX級別和基礎框架都直接提供了條件的支援。(如果你使用操作物件,你可以配置你的操作物件之間的依賴關係的順序確定任務的執行順序,這和條件提供的行為非常相似)。
儘管鎖和條件在併發設計中使用非常普遍,原子操作也是另外一種保護和同步訪問資料的方法。原子操作在以下情況的時候提供了替代鎖的輕量級的方法,其中你可以執行標量資料型別的數學或邏輯運算。原子操作使用特殊的硬體設施來保證變數的改變在其他執行緒可以訪問之前完成。
執行緒間通訊
執行緒間通訊有很多種方法,每種都有它的優點和缺點。
1、Direct messaging
Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources, see “Cocoa Perform Selector Sources.”
2、Global variables, shared memory, and objects
Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.
3、Conditions
Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread run only when the stated condition is met. For information on how to use conditions, see “Using Conditions.”
4、Run loop sources
A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources, see “Run Loops.”
5、Ports and sockets
Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see “Run Loops.”
6、Message queues
The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide.
7、Cocoa distributed objects
Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread communication, doing so is highly discouraged because of the amount of overhead it incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, seeDistributed Objects Programming Topics.
設計技巧
1、避免顯式建立執行緒
手動編寫執行緒建立程式碼是乏味的,而且容易出現錯誤,你應該儘可能避免這樣做。Mac OS X和iOS通過其他API介面提供了隱式的併發支援。你可以考慮使用非同步API,GCD方式,或操作物件來實現併發,而不是自己建立一個執行緒。這些技術背後為你做了執行緒相關的工作,並保證是無誤的。此外,比如GCD和操作物件技術被設計用來管理執行緒,比通過自己的程式碼根據當前的負載調整活動執行緒的數量更高效。 關於更多GCD和操作物件的資訊,你可以查閱“併發程式設計指南(Concurrency Programming Guid)”。
2、保持你的執行緒合理的忙
如果你準備人工建立和管理執行緒,記得多執行緒消耗系統寶貴的資源。你應該盡最大努力確保任何你分配到執行緒的任務是執行相當長時間和富有成效的。同時你不應該害怕中斷那些消耗最大空閒時間的執行緒。
3、 避免共享資料結構
避免造成執行緒相關資源衝突的最簡單最容易的辦法是給你應用程式的每個執行緒一份它需求的資料的副本。最小化執行緒之間的通訊和資源爭奪時並行程式碼的效果最好。
4、多執行緒和你的使用者介面
如果你的應用程式具有一個圖形使用者介面,建議你在主執行緒裡面接收和介面相關的事件和初始化更新你的介面。這種方法有助於避免與處理使用者事件和視窗繪圖相關的同步問題。一些框架,比如Cocoa,通常需要這樣操作,但是它的事件處理可以不這樣做,在主執行緒上保持這種行為的優勢在於簡化了管理你應用程式使用者介面的邏輯。
有幾個顯著的例外,它有利於在其他執行緒執行圖形操作。比如,QuickTime API包含了一系列可以在輔助執行緒執行的操作,包括開啟視訊檔案,渲染視訊檔案,壓縮視訊檔案,和匯入匯出影象。類似的,在Carbon和Cocoa裡面,你可以使用輔助執行緒來建立和處理圖片和其他圖片相關的計算。使用輔助執行緒來執行這些操作可以極大提高效能。如果你不確定一個操作是否和影象處理相關,那麼你應該在主執行緒執行這些操作。
關於QuickTime執行緒安全的資訊,查閱Technical Note TN2125:“QuickTime的執行緒安全程式設計”。關於Cocoa執行緒安全的更多資訊,查閱“執行緒安全總結”。關於Cocoa繪畫資訊,查閱Cocoa繪畫指南(Cocoa Drawing Guide)。
5、瞭解執行緒退出時的行為
程式一直執行直到所有非獨立執行緒都已經退出為止。預設情況下,只有應用程式的主執行緒是以非獨立的方式建立的,但是你也可以使用同樣的方法來建立其他執行緒。當使用者退出程式的時候,通常考慮適當的立即中斷所有獨立執行緒,因為通常獨立執行緒所做的工作都是是可選的。如果你的應用程式使用後臺執行緒來儲存資料到硬碟或者做其他週期行的工作,那麼你可能想把這些執行緒建立為非獨立的來保證程式退出的時候不丟失資料。
以非獨立的方式建立執行緒(又稱作為可連線的)你需要做一些額外的工作。因為大部分上層執行緒封裝技術預設情況下並沒有提供建立可連線的執行緒,你必須使用POSIX API來建立你想要的執行緒。此外,你必須在你的主執行緒新增程式碼,來當它們最終退出的時候連線非獨立的執行緒。更多有關建立可連線的執行緒資訊,請查閱“設定執行緒的脫離狀態”部分。
如果你正在程式設計Cocoa的程式,你也可以通過使用applicationShouldTerminate:的委託方法來延遲程式的中斷直到一段時間後或者完成取消。當延遲中斷的時候,你的程式需要等待直到任何週期執行緒已經完成它們的任務且呼叫了replyToApplicationShouldTerminate:方法。關於更多這些方法的資訊,請查閱NSApplication Class Reference。
6、處理異常
當丟擲一個異常時,異常的處理機制依賴於當前呼叫堆疊執行任何必要的清理。因為每個執行緒都有它自己的呼叫堆疊,所以每個執行緒都負責捕獲它自己的異常。如果在輔助執行緒裡面捕獲一個丟擲的異常失敗,那麼你的主執行緒也同樣捕獲該異常失敗:它所屬的程式就會中斷。你無法捕獲同一個程式裡面其他執行緒丟擲的異常。
如果你需要通知另一個執行緒(比如主執行緒)當前執行緒中的一個特殊情況,你應該捕捉異常,並簡單地將訊息傳送到其他執行緒告知發生了什麼事。根據你的模型和你正在嘗試做的事情,引發異常的執行緒可以繼續執行(如果可能的話),等待指示,或者乾脆退出。
7、乾淨地中斷你的執行緒
執行緒自然退出的最好方式是讓它達到其主入口結束點。雖然有不少函式可以用來立即中斷執行緒,但是這些函式應僅用於作為最後的手段。線上程達到它自然結束點之前中斷一個執行緒阻礙該執行緒清理完成它自己。如果執行緒已經分配了記憶體,開啟了檔案,或者獲取了其他型別資源,你的程式碼可能沒辦法回收這些資源,結果造成記憶體洩漏或者其他潛在的問題。
關於更多正確退出執行緒的資訊,請查閱“中斷執行緒”部分。
8、 執行緒安全的庫
雖然應用程式開發人員控制應用程式是否執行多個執行緒,類庫的開發者則無法這樣控制。當開發類庫時,你必須假設呼叫應用程式是多執行緒,或者多執行緒之間可以隨時切換。因此你應該總是在你的臨界區使用鎖功能。
對類庫開發者而言,只當應用程式是多執行緒的時候才建立鎖是不明智的。如果你需要鎖定你程式碼中的某些部分,早期應該建立鎖物件給你的類庫使用,更好是顯式呼叫初始化類庫。雖然你也可以使用靜態庫的初始化函式來建立這些鎖,但是僅當沒有其他方式的才應該這樣做。執行初始化函式需要延長載入你類庫的時間,且可能對你程式效能造成不利影響。