一、什么是線程?
線程是一種相對輕量級的方法,可以在應(yīng)用程序內(nèi)實(shí)現(xiàn)多個執(zhí)行路徑。在系統(tǒng)級別,程序并排運(yùn)行,系統(tǒng)根據(jù)每個程序的需求和其他程序的需求為每個程序分配執(zhí)行時間。然而,每個程序內(nèi)部都存在一個或多個執(zhí)行線程,可用于同時或幾乎同時執(zhí)行不同的任務(wù)。系統(tǒng)本身實(shí)際上管理這些執(zhí)行線程,將它們安排在可用內(nèi)核上運(yùn)行,并根據(jù)需要先發(fā)制人地中斷它們,以允許其他線程運(yùn)行。
從技術(shù)角度來看,線程是管理代碼執(zhí)行所需的內(nèi)核級和應(yīng)用程序級數(shù)據(jù)結(jié)構(gòu)的組合。內(nèi)核級結(jié)構(gòu)協(xié)調(diào)向線程發(fā)送事件和線程在可用內(nèi)核之一上的先發(fā)制人調(diào)度。應(yīng)用程序級結(jié)構(gòu)包括用于存儲函數(shù)調(diào)用的調(diào)用堆棧,以及應(yīng)用程序管理和操作線程屬性和狀態(tài)所需的結(jié)構(gòu)。
在非并發(fā)應(yīng)用程序中,只有一個執(zhí)行線程。該線程以應(yīng)用程序main例程開始和結(jié)束,并逐個分支到不同的方法或函數(shù),以實(shí)現(xiàn)應(yīng)用程序的整體行為。相比之下,支持并發(fā)的應(yīng)用程序從一個線程開始,并根據(jù)需要添加更多內(nèi)容來創(chuàng)建額外的執(zhí)行路徑。每個新路徑都有自己的自定義啟動例程,獨(dú)立于應(yīng)用程序main例程中的代碼運(yùn)行。在應(yīng)用程序中擁有多個線程有兩個非常重要的潛在優(yōu)勢:
多個線程可以提高應(yīng)用程序的感知響應(yīng)能力。
多個線程可以提高應(yīng)用程序在多核系統(tǒng)的實(shí)時性能。
如果您的應(yīng)用程序只有一個線程,則該線程必須執(zhí)行所有操作。它必須響應(yīng)事件,更新應(yīng)用程序的窗口,并執(zhí)行實(shí)現(xiàn)應(yīng)用程序行為所需的所有計算。只有一個線程的問題是它一次只能做一件事。那么,當(dāng)您的計算需要很長時間才能完成時,會發(fā)生什么?當(dāng)您的代碼忙于計算所需的值時,應(yīng)用程序會停止響應(yīng)用戶事件并更新其窗口。如果這種行為持續(xù)足夠長的時間,用戶可能會認(rèn)為您的應(yīng)用程序被掛起,并嘗試強(qiáng)制退出。然而,如果您將自定義計算移動到單獨(dú)的線程上,應(yīng)用程序的主線程將可以更及時地自由響應(yīng)用戶交互。
由于現(xiàn)在多核計算機(jī)很常見,線程提供了一種提高某些類型應(yīng)用程序性能的方法。執(zhí)行不同任務(wù)的線程可以在不同的處理器核心上同時執(zhí)行,使應(yīng)用程序能夠在給定的時間內(nèi)增加工作量。
當(dāng)然,線程不是修復(fù)應(yīng)用程序性能問題的靈丹妙藥。隨著線程帶來的好處,還帶來了潛在的問題。在應(yīng)用程序中擁有多個執(zhí)行路徑可能會使代碼增加相當(dāng)大的復(fù)雜性。每個線程必須與其他線程協(xié)調(diào)其操作,以防止其損壞應(yīng)用程序的狀態(tài)信息。由于單個應(yīng)用程序中的線程共享相同的內(nèi)存空間,因此它們可以訪問所有相同的數(shù)據(jù)結(jié)構(gòu)。如果兩個線程試圖同時操作相同的數(shù)據(jù)結(jié)構(gòu),一個線程可能會以損壞生成的數(shù)據(jù)結(jié)構(gòu)的方式覆蓋另一個線程的更改。即使有適當(dāng)?shù)谋Wo(hù),您仍然需要注意編譯器優(yōu)化,這些優(yōu)化會將微妙(而不是那么微妙)的錯誤引入代碼。
1.線程術(shù)語
線程用于指代碼的單獨(dú)執(zhí)行路徑。進(jìn)程用于指正在運(yùn)行的可執(zhí)行文件,該可執(zhí)行文件可以包含多個線程。任務(wù)一詞用于指需要執(zhí)行的抽象工作概念。
2.線程替代方案
Grand Central Dispatch在Mac OS x v10.6中推出,是線程的一種替代方案,允許您專注于需要執(zhí)行的任務(wù),而不是線程管理。使用GCD,您可以定義要執(zhí)行的任務(wù)并將其添加到工作隊列中,該隊列處理在適當(dāng)線程上的任務(wù)調(diào)度。工作隊列考慮了可用內(nèi)核的數(shù)量和當(dāng)前負(fù)載,以比您自己使用線程更有效地執(zhí)行任務(wù)。
3.運(yùn)行循環(huán)
運(yùn)行循環(huán)是用于管理異步到達(dá)線程的事件的基礎(chǔ)設(shè)施。運(yùn)行循環(huán)通過監(jiān)控線程的一個或多個事件源來工作。當(dāng)事件到達(dá)時,系統(tǒng)喚醒線程并將事件調(diào)度到運(yùn)行循環(huán),然后運(yùn)行循環(huán)將它們調(diào)度到您指定的處理程序。如果沒有事件并準(zhǔn)備處理,運(yùn)行循環(huán)會將線程置于睡眠狀態(tài)。
您不需要對創(chuàng)建的任何線程使用運(yùn)行循環(huán),但這樣做可以為用戶提供更好的體驗。運(yùn)行循環(huán)可以創(chuàng)建使用最少資源的長壽命線程。由于運(yùn)行循環(huán)在無事可做時將其線程置于睡眠狀態(tài),因此它消除了輪詢的需求,輪詢浪費(fèi)了CPU循環(huán),并防止處理器本身進(jìn)入睡眠和節(jié)省電力。
要配置運(yùn)行循環(huán),您只需啟動線程,獲取對運(yùn)行循環(huán)對象的引用,安裝事件處理程序,并告訴運(yùn)行循環(huán)運(yùn)行。OS X提供的基礎(chǔ)架構(gòu)會自動為您處理主線程運(yùn)行循環(huán)的配置。
有關(guān)運(yùn)行循環(huán)的詳細(xì)信息以及如何使用它們的示例在運(yùn)行循環(huán)中提供。
4.同步工具
線程編程的危害之一是多個線程之間的資源爭用。如果多個線程試圖同時使用或修改同一資源,可能會出現(xiàn)問題。緩解這個問題的一個方法是完全消除共享資源,并確保每個線程都有自己的一組不同的資源來操作。不過,當(dāng)維護(hù)完全獨(dú)立的資源不可行時,您可能不得不使用鎖、條件、原子操作同步對資源的訪問。
鎖為一次只能由一個線程執(zhí)行的代碼提供了一種蠻力形式的保護(hù)。最常見的鎖類型是互斥鎖,也稱為互斥鎖。當(dāng)線程試圖獲取當(dāng)前由另一個線程持有的互斥體時,它會阻止,直到另一個線程釋放鎖。幾個系統(tǒng)框架為互斥鎖提供支持,盡管它們都基于相同的底層技術(shù)。此外,Cocoa提供了互斥鎖的幾種變體,以支持不同類型的行為,如遞歸。有關(guān)可用鎖類型的更多信息,請參閱鎖。
除了鎖外,系統(tǒng)還支持條件,確保應(yīng)用程序中任務(wù)的適當(dāng)排序。條件充當(dāng)守門人,阻止給定的線程,直到它所代表的條件變成真。當(dāng)這種情況發(fā)生時,條件會釋放線程并允許它繼續(xù)。POSIX層和基礎(chǔ)框架都為條件提供了直接支持。(如果您使用操作對象,您可以在操作對象之間配置依賴項來對任務(wù)的執(zhí)行進(jìn)行排序,這與條件提供的行為非常相似。)
雖然鎖和條件在并發(fā)設(shè)計中非常常見,但原子操作是保護(hù)和同步數(shù)據(jù)訪問的另一種方式。在您可以對標(biāo)量數(shù)據(jù)類型(scalar data types)(OC中基本數(shù)據(jù)類型)執(zhí)行數(shù)學(xué)或邏輯操作的情況下,原子操作提供了鎖的輕量級替代方案。原子操作使用特殊的硬件指令,以確保在其他線程有機(jī)會訪問變量之前完成對變量的修改。
有關(guān)可用同步工具的更多信息,請參閱同步工具。
5.線程間通信
雖然良好的設(shè)計最大限度地減少了所需的通信量,但在某些時候,線程之間的通信變得必要。(線程的工作是為您的應(yīng)用程序工作,但如果該工作的結(jié)果從未被使用過,它有什么好處?)線程可能需要處理新的工作請求或向應(yīng)用程序的主線程報告其進(jìn)度。在這些情況下,您需要一種方法來獲取信息從一個線程到另一個線程。幸運(yùn)的是,線程共享相同的進(jìn)程空間這一事實(shí)意味著您有很多通信選項。
線程之間有很多溝通方式,每種方式都有各自的優(yōu)缺點(diǎn)。配置線程本地存儲列出了您可以在OS X中使用的最常見的通信機(jī)制。(除消息隊列外,這些技術(shù)在iOS中也可用。)本表中的技術(shù)按復(fù)雜性的增加順序列出。
通信機(jī)制
| 機(jī)制 | 描述 |
|---|---|
| 直接消息 | Cocoa應(yīng)用程序支持直接在其他線程上執(zhí)行選擇器的能力。此功能意味著一個線程基本上可以在任何其他線程上執(zhí)行方法。由于它們是在目標(biāo)線程的上下文中執(zhí)行的,因此以這種方式發(fā)送的消息會自動在該線程上序列化。有關(guān)輸入源的信息,請參閱可可執(zhí)行選擇器源。 |
| 全局變量、共享內(nèi)存和對象 | 在兩個線程之間通信信息的另一種簡單方法是使用全局變量、共享對象或共享內(nèi)存塊。雖然共享變量快速簡單,但它們也比直接消息傳遞更脆弱。共享變量必須用鎖或其他同步機(jī)制仔細(xì)保護(hù),以確保代碼的正確性。如果不這樣做,可能會導(dǎo)致種族狀況、數(shù)據(jù)損壞或崩潰。 |
| 條件 | 條件是一種同步工具,可用于控制線程何時執(zhí)行特定部分代碼。您可以將條件視為門衛(wèi),只在滿足所述條件時讓線程運(yùn)行。有關(guān)如何使用條件的信息,請參閱使用條件。 |
| 運(yùn)行循環(huán)源 | 自定義運(yùn)行循環(huán)源是您為在線程上接收特定于應(yīng)用程序的消息而設(shè)置的源。由于它們是事件驅(qū)動的,運(yùn)行循環(huán)源在無事可做時自動將線程置于睡眠狀態(tài),從而提高線程的效率。有關(guān)運(yùn)行循環(huán)和運(yùn)行循環(huán)源的信息,請參閱運(yùn)行循環(huán)。 |
| 端口和套接字 | $基于端口的通信是兩個線程之間更復(fù)雜的通信方式,但它也是一種非??煽康募夹g(shù)。更重要的是,端口和套接字可用于與其他流程和服務(wù)等外部實(shí)體通信。為了提高效率,端口使用運(yùn)行循環(huán)源實(shí)現(xiàn),因此當(dāng)端口上沒有數(shù)據(jù)等待時,線程會進(jìn)入睡眠狀態(tài)。有關(guān)運(yùn)行循環(huán)和基于端口的輸入源的信息,請參閱運(yùn)行循環(huán)。 |
| 消息隊列 | 傳統(tǒng)的多處理服務(wù)定義了用于管理傳入和傳出數(shù)據(jù)的先進(jìn)先出(FIFO)隊列抽象。雖然消息隊列簡單方便,但它們不如其他一些通信技術(shù)高效。有關(guān)如何使用消息隊列的更多信息,請參閱多處理服務(wù)編程指南。 |
6.保持線程合理繁忙
如果您決定手動創(chuàng)建和管理線程,請記住線程消耗寶貴的系統(tǒng)資源。您應(yīng)該盡最大努力確保分配給線程的任何任務(wù)都是合理的長壽和高效的。與此同時,您不應(yīng)該害怕終止大部分時間閑置的線程。線程使用數(shù)量不平凡的內(nèi)存,其中一些是有線的,因此釋放空閑線程不僅有助于減少應(yīng)用程序的內(nèi)存占用空間,還可以釋放更多的物理內(nèi)存供其他系統(tǒng)進(jìn)程使用。
二、線程管理
iOS中的每個進(jìn)程(應(yīng)用程序)都由一個或多個線程組成,每個線程代表通過應(yīng)用程序代碼執(zhí)行的單一路徑。每個應(yīng)用程序都以一個線程開頭,該線程運(yùn)行應(yīng)用程序main功能。應(yīng)用程序可以生成額外的線程,每個線程都執(zhí)行特定函數(shù)的代碼。
當(dāng)應(yīng)用程序生成新線程時,該線程將成為應(yīng)用程序進(jìn)程空間內(nèi)的獨(dú)立實(shí)體。每個線程都有自己的執(zhí)行堆棧,由內(nèi)核單獨(dú)安排運(yùn)行時。線程可以與其他線程和其他進(jìn)程通信,執(zhí)行I/O操作,并執(zhí)行您可能需要它執(zhí)行的任何其他操作。然而,由于它們位于同一個進(jìn)程空間內(nèi),單個應(yīng)用程序中的所有線程共享相同的虛擬內(nèi)存空間,并擁有與進(jìn)程本身相同的訪問權(quán)限。
本章概述了iOS中可用的線程技術(shù),以及如何在應(yīng)用程序中使用這些技術(shù)的示例。
1.線程成本
就內(nèi)存使用和性能而言,線程對您的程序(和系統(tǒng))來說是真正的成本。每個線程都需要在內(nèi)核內(nèi)存空間和程序的內(nèi)存空間中分配內(nèi)存。管理線程和協(xié)調(diào)其調(diào)度所需的核心結(jié)構(gòu)使用有線內(nèi)存(wired memory)存儲在內(nèi)核中。線程的堆??臻g和每個線程數(shù)據(jù)存儲在程序的內(nèi)存空間中。這些結(jié)構(gòu)大多是在您首次創(chuàng)建線程時創(chuàng)建和初始化的——由于需要與內(nèi)核交互,這個過程可能相對昂貴。
表2-1量化了在應(yīng)用程序中創(chuàng)建新的用戶級線程的大致成本。其中一些成本是可配置的,例如為輔助線程分配的堆??臻g量。創(chuàng)建線程的時間成本是一個粗略的近似值,只應(yīng)用于彼此的相對比較。線程創(chuàng)建時間可能因處理器負(fù)載、計算機(jī)速度以及可用系統(tǒng)和程序內(nèi)存量而有很大差異。
表2-1線程創(chuàng)建成本
| 項目 | 大約成本 | 筆記 |
|---|---|---|
| 內(nèi)核數(shù)據(jù)結(jié)構(gòu) | 大約1KB | 此內(nèi)存用于存儲線程數(shù)據(jù)結(jié)構(gòu)和屬性,其中大部分被分配為有線內(nèi)存,因此無法分頁到磁盤。 |
| 堆??臻g | 512 KB(次機(jī)線程) 8 MB(OS X主線程) 1 MB(iOS主線程) |
輔助線程允許的最小堆棧大小為16KB,堆棧大小必須是4KB的倍數(shù)。在線程創(chuàng)建時,此內(nèi)存的空間將預(yù)留到您的進(jìn)程空間中,但與該內(nèi)存關(guān)聯(lián)的實(shí)際頁面要到需要時才會創(chuàng)建。 |
| 創(chuàng)建時間 | 大約90微秒 | 此值反映了創(chuàng)建線程的初始調(diào)用與線程的入口點(diǎn)例程開始執(zhí)行之間的時間。這些數(shù)字是通過分析基于英特爾的配備2GHz Core Duo處理器和運(yùn)行OS X v10.5的1 GB內(nèi)存的基于英特爾的iMac上創(chuàng)建線程時生成的平均值和中值來確定的。 |
| 水果 | 價格 | 數(shù)量 |
|---|---|---|
| 香蕉 | $1 | 5 |
| 蘋果 | $1 | 6 |
| 草莓 | $1 | 7 |