線程管理Mac OS X和iOS里面的每個(gè)進(jìn)程都是有一個(gè)或多個(gè)線程構(gòu)成,每個(gè)線程都代表一個(gè)代碼的執(zhí)行路徑。每個(gè)應(yīng)用程序啟動(dòng)時(shí)候都是一個(gè)線程,它執(zhí)行程序的main函數(shù)。應(yīng)用程序可以生成額外的線程,其中每個(gè)線程執(zhí)行一個(gè)特定功能的代碼。 當(dāng)應(yīng)用程序生成一個(gè)新的線程的時(shí)候,該線程變成應(yīng)用程序進(jìn)程空間內(nèi)的一個(gè)實(shí)體。每個(gè)線程都擁有它自己的執(zhí)行堆棧,由內(nèi)核調(diào)度獨(dú)立的運(yùn)行時(shí)間片。一個(gè)線程可以和其他線程或其他進(jìn)程通信,執(zhí)行I/O操作,甚至執(zhí)行任何你想要它完成的任務(wù)。因?yàn)樗鼈兲幱谙嗤倪M(jìn)程空間,所以一個(gè)獨(dú)立應(yīng)用程序里面的所有線程共享相同的虛擬內(nèi)存空間,并且具有和進(jìn)程相同的訪問權(quán)限。 本章提供了Mac OS X和iOS上面可用線程技術(shù)的預(yù)覽,并給出了如何在你的應(yīng)用程序里面使用它們的例子。
1.1 線程成本多線程會(huì)占用你應(yīng)用程序(和系統(tǒng)的)的內(nèi)存使用和性能方面的資源。每個(gè)線程都需要分配一定的內(nèi)核內(nèi)存和應(yīng)用程序內(nèi)存空間的內(nèi)存。管理你的線程和協(xié)調(diào)其調(diào)度所需的核心數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)在使用Wired Memory的內(nèi)核里面。你線程的堆棧空間和每個(gè)線程的數(shù)據(jù)都被存儲(chǔ)在你應(yīng)用程序的內(nèi)存空間里面。這些數(shù)據(jù)結(jié)構(gòu)里面的大部分都是當(dāng)你首次創(chuàng)建線程或者進(jìn)程的時(shí)候被創(chuàng)建和初始化的,它們所需的代價(jià)成本很高,因?yàn)樾枰蛢?nèi)核交互。 表2-1量化了在你應(yīng)用程序創(chuàng)建一個(gè)新的用戶級(jí)線程所需的大致成本。這些成本里面的部分是可配置的,比如為輔助線程分配堆棧空間的大小。創(chuàng)建一個(gè)線程所需的時(shí)間成本是粗略估計(jì)的,僅用于當(dāng)互相比較的時(shí)候。線程創(chuàng)建時(shí)間很大程度依賴于處理器的負(fù)載,計(jì)算速度,和可用的系統(tǒng)和程序空間。 Table 2-1 Thread creation costs
注意:因?yàn)榈讓觾?nèi)核的支持,操作對(duì)象(Operation objectis)可能創(chuàng)建線程更快。它們使用內(nèi)核里面常駐線程池里面的線程來節(jié)省創(chuàng)建的時(shí)間,而不是每次都創(chuàng)建新的線程。關(guān)于更多使用操作對(duì)象(Operation objects)的信息,參閱并發(fā)編程指南(Concurrency Programming Guide)。 當(dāng)編寫線程代碼時(shí)另外一個(gè)需要考慮的成本是生產(chǎn)成本。設(shè)計(jì)一個(gè)線程應(yīng)用程序有時(shí)會(huì)需要根本性改變你應(yīng)用程序數(shù)據(jù)結(jié)構(gòu)的組織方式。要做這些改變可能需要避免使用同步,因?yàn)楸旧碓O(shè)計(jì)不好的應(yīng)用可能會(huì)造成巨大的性能損失。設(shè)計(jì)這些數(shù)據(jù)結(jié)構(gòu)和在線程代碼里面調(diào)試問題會(huì)增加開發(fā)一個(gè)線程應(yīng)用所需的時(shí)間。然而避免這些消耗的話,可能在運(yùn)行時(shí)候帶來更大的問題,如果你的多線程花費(fèi)太多的時(shí)間在鎖的等待而沒有做任何事情。 1.1 創(chuàng)建一個(gè)線程創(chuàng)建低級(jí)別的線程相對(duì)簡(jiǎn)單。在所有情況下,你必須有一個(gè)函數(shù)或方法作為線程的主入口點(diǎn),你必須使用一個(gè)可用的線程例程啟動(dòng)你的線程。以下幾個(gè)部分介紹了比較常用線程創(chuàng)建的基本線程技術(shù)。線程創(chuàng)建使用了這些技術(shù)的繼承屬性的默認(rèn)設(shè)置,由你所使用的技術(shù)來決定。關(guān)于更多如何配置你的線程的信息,參閱“線程屬性配置”部分。 1.1.1 使用NSThread使用NSThread來創(chuàng)建線程有兩個(gè)可以的方法:
這兩種創(chuàng)建線程的技術(shù)都在你的應(yīng)用程序里面新建了一個(gè)脫離的線程。一個(gè)脫離的線程意味著當(dāng)線程退出的時(shí)候線程的資源由系統(tǒng)自動(dòng)回收。這也同樣意味著之后不需要在其他線程里面顯式的連接(join)。因?yàn)閐etachNewThreadSelctor:toTarget:withObject:方法在Mac OS X的任何版本都支持,所以在Cocoa應(yīng)用里面使用多線程的地方經(jīng)??梢园l(fā)現(xiàn)它。為了生成一個(gè)新的線程,你只要簡(jiǎn)單的提供你想要使用為線程主體入口的方法的名稱(被指定為一個(gè)selector),和任何你想在啟動(dòng)時(shí)傳遞給線程的數(shù)據(jù)。下面的示例演示了這種方法的基本調(diào)用,來使用當(dāng)前對(duì)象的自定義方法來生成一個(gè)線程。
在Mac OS X v10.5及其之后初始化一個(gè)NSThread對(duì)象的簡(jiǎn)單方法是使用initWithTarget:selector:object:方法。該方法和detachNewThreadSelector:toTarget:withObject:方法來初始化一個(gè)新的NSThread實(shí)例需要相同的額外開銷。然而它并沒有啟動(dòng)一個(gè)線程。為了啟動(dòng)一個(gè)線程,你可以顯式調(diào)用先對(duì)象的start方法,如下面代碼: 注意:使用initWithTarget:selector:object:方法的替代辦法是子類化NSThread,并重寫它的main方法。你可以使用你重寫的該方法的版本來實(shí)現(xiàn)你線程的主體入口。更多信息,請(qǐng)參閱NSThread Class Reference里面子類化的提示。 如果你擁有一個(gè)NSThread對(duì)象,它的線程當(dāng)前真正運(yùn)行,你可以給該線程發(fā)送消息的唯一方法是在你應(yīng)用程序里面的任何對(duì)象使用performSelector:onThread:withObject:waitUntilDone:方法。在Mac OS X v10.5支持在多線程上面執(zhí)行selectors(而不是在主線程里面),并且它是實(shí)現(xiàn)線程間通信的便捷方法。你使用該技術(shù)時(shí)所發(fā)送的消息會(huì)被其他線程作為run-loop主體的一部分直接執(zhí)行(當(dāng)然這些意味著目標(biāo)線程必須在它的run loop里面運(yùn)行,參閱“ Run Loops”)。當(dāng)你使用該方法來實(shí)現(xiàn)線程通信的時(shí)候,你可能仍然需要一個(gè)同步操作,但是這比在線程間設(shè)置通信端口簡(jiǎn)單多了。 注意:雖然在線程間的偶爾通信的時(shí)候使用該方法很好,但是你不能周期的或頻繁的使用performSelector:onThread:withObject:waitUntilDone:來實(shí)現(xiàn)線程間的通信。 關(guān)于線程間通信的可選方法,參閱“設(shè)置線程的脫離狀態(tài)”部分。 1.1.2 使用POSIX的多線程Mac OS X和iOS提供基于C語言支持的使用POSIX線程API來創(chuàng)建線程的方法。該技術(shù)實(shí)際上可以被任何類型的應(yīng)用程序使用(包括Cocoa和Cocoa Touch的應(yīng)用程序),并且如果你當(dāng)前真為多平臺(tái)開發(fā)應(yīng)用的話,該技術(shù)可能更加方便。你使用來創(chuàng)建線程的POSIX例程被調(diào)用的時(shí)候,使用pthread_create剛好足夠。 列表2-1顯示了兩個(gè)使用POSIX來創(chuàng)建線程的自定義函數(shù)。LaunchThread函數(shù)創(chuàng)建了一個(gè)新的線程,該線程的例程由PosixThreadMainRoutine函數(shù)來實(shí)現(xiàn)。因?yàn)镻OSIX創(chuàng)建的線程默認(rèn)情況是可連接的(joinable),下面的例子改變線程的屬性來創(chuàng)建一個(gè)脫離的線程。把線程標(biāo)記為脫離的,當(dāng)它退出的時(shí)候讓系統(tǒng)有機(jī)會(huì)立即回收該線程的資源。 Listing 2-1 Creating a thread in C
如果你把上面列表的代碼添加到你任何一個(gè)源文件,并且調(diào)用LaunchThread函數(shù),它將會(huì)在你的應(yīng)用程序里面創(chuàng)建一個(gè)新的脫離線程。當(dāng)然,新創(chuàng)建的線程使用該代碼沒有做任何有用的事情。線程將會(huì)加載并立即退出。為了讓它更有興趣,你需要添加代碼到PosixThreadMainRoutine函數(shù)里面來做一些實(shí)際的工作。為了保證線程知道該干什么,你可以在創(chuàng)建的時(shí)候給線程傳遞一個(gè)數(shù)據(jù)的指針。把該指針作為pthread_create的最后一個(gè)參數(shù)。 為了在新建的線程里面和你應(yīng)用程序的主線程通信,你需要建立一條和目標(biāo)線程之間的穩(wěn)定的通信路徑。對(duì)于基于C語言的應(yīng)用程序,有幾種辦法來實(shí)現(xiàn)線程間的通信,包括使用端口(ports),條件(conditions)和共享內(nèi)存(shared memory)。對(duì)于長(zhǎng)期存在的線程,你應(yīng)該幾乎總是成立某種線程間的通信機(jī)制,讓你的應(yīng)用程序的主線程有辦法來檢查線程的狀態(tài)或在應(yīng)用程序退出時(shí)干凈關(guān)閉它。 關(guān)于更多介紹POSIX線程函數(shù)的信息,參閱pthread的主頁。 1.1.3 使用NSObject來生成一個(gè)線程在iOS和Mac OS X v10.5及其之后,所有的對(duì)象都可能生成一個(gè)新的線程,并用它來執(zhí)行它任意的方法。方法performSelectorInBackground:withObject:新生成一個(gè)脫離的線程,使用指定的方法作為新線程的主體入口點(diǎn)。比如,如果你有一些對(duì)象(使用變量myObj來代表),并且這些對(duì)象擁有一個(gè)你想在后臺(tái)運(yùn)行的doSomething的方法,你可以使用如下的代碼來生成一個(gè)新的線程: 調(diào)用該方法的效果和你在當(dāng)前對(duì)象里面使用NSThread的detachNewThreadSelector:toTarget:withObject:傳遞selectore,object作為參數(shù)的方法一樣。新的線程將會(huì)被立即生成并運(yùn)行,它使用默認(rèn)的設(shè)置。在selectore內(nèi)部,你必須配置線程就像你在任何線程里面一樣。比如,你可能需要設(shè)置一個(gè)自動(dòng)釋放池(如果你沒有使用垃圾回收機(jī)制),在你要使用它的時(shí)候配置線程的run loop。關(guān)于更是介紹如果配置線程的信息,參閱“配置線程屬性”部分。 1.1.4 使用其他線程技術(shù)盡管POSIX例程和NSThread類被推薦使用來創(chuàng)建低級(jí)線程,但是其他基于C語言的技術(shù)在Mac OS X上面同樣可用。在這其中,唯一一個(gè)可以考慮使用的是多處理服務(wù)(Multiprocessing Services),它本身就是在POSIX線程上執(zhí)行。多處理服務(wù)是專門為早期的Mac OS版本開發(fā)的,后來在Mac OS X里面的Carbon應(yīng)用程序上面同樣適用。如果你有代碼真是有該技術(shù),你可以繼續(xù)使用它,盡管你應(yīng)該把這些代碼轉(zhuǎn)化為POSIX。該技術(shù)在iOS上面不可用。 關(guān)于更多如何使用多處理服務(wù)的信息,參閱多處理服務(wù)編程指南(Multiprocessing Services Programming Guide)。 1.1.5 在Cocoa程序上面使用POSIX線程經(jīng)管NSThread類是Cocoa應(yīng)用程序里面創(chuàng)建多線程的主要接口,如果可以更方便的話你可以任意使用POSIX線程帶替代。例如,如果你的代碼里面已經(jīng)使用了它,而你又不想改寫它的話,這時(shí)你可能需要使用POSIX多線程。如果你真打算在Cocoa程序里面使用POSIX線程,你應(yīng)該了解如果在Cocoa和線程間交互,并遵循以下部分的一些指南。 u Cocoa框架的保護(hù) 對(duì)于多線程的應(yīng)用程序,Cocoa框架使用鎖和其他同步方式來保證代碼的正確執(zhí)行。為了保護(hù)這些鎖造成在單線程里面性能的損失,Cocoa直到應(yīng)用程序使用NSThread類生成它的第一個(gè)新的線程的時(shí)候才創(chuàng)建這些鎖。如果你僅且使用POSIX例程來生成新的線程,Cocoa不會(huì)收到關(guān)于你的應(yīng)用程序當(dāng)前變?yōu)槎嗑€程的通知。當(dāng)這些剛好發(fā)生的時(shí)候,涉及Cocoa框架的操作哦可能會(huì)破壞甚至讓你的應(yīng)用程序崩潰。 為了讓Cocoa知道你正打算使用多線程,你所需要做的是使用NSThread類生成一個(gè)線程,并讓它立即退出。你線程的主體入口點(diǎn)不需要做任何事情。只需要使用NSThread來生成一個(gè)線程就足夠保證Cocoa框架所需的鎖到位。 如果你不確定Cocoa是否已經(jīng)知道你的程序是多線程的,你可以使用NSThread的isMultiThreaded方法來檢驗(yàn)一下。 u 混合POSIX和Cocoa的鎖 在同一個(gè)應(yīng)用程序里面混合使用POSIX和Cocoa的鎖很安全。Cocoa鎖和條件對(duì)象基本上只是封裝了POSIX的互斥體和條件。然而給定一個(gè)鎖,你必須總是使用同樣的接口來創(chuàng)建和操縱該鎖。換言之,你不能使用Cocoa的NSLock對(duì)象來操縱一個(gè)你使用pthread_mutex_init函數(shù)生成的互斥體,反之亦然。 1.2 配置線程屬性創(chuàng)建線程之后,或者有時(shí)候是之前,你可能需要配置不同的線程環(huán)境。以下部分描述了一些你可以做的改變,和在什么時(shí)候你需要做這些改變。 1.2.1 配置線程的堆棧大小對(duì)于每個(gè)你新創(chuàng)建的線程,系統(tǒng)會(huì)在你的進(jìn)程空間里面分配一定的內(nèi)存作為該線程的堆棧。該堆棧管理堆棧幀,也是任何線程局部變量聲明的地方。給線程分配的內(nèi)存大小在“線程成本”里面已經(jīng)列舉了。 如果你想要改變一個(gè)給定線程的堆棧大小,你必須在創(chuàng)建該線程之前做一些操作。所有的線程技術(shù)提供了一些辦法來設(shè)置線程堆棧的大小。雖然可以使用NSThread來設(shè)置堆棧大小,但是它只能在iOS和Mac OS X v10.5及其之后才可用。表2-2列出了每種技術(shù)的對(duì)于不同的操作。 Table 2-2 Setting the stack size of a thread
1.2.2 配置線程本地存儲(chǔ)每個(gè)線程都維護(hù)了一個(gè)鍵-值的字典,它可以在線程里面的任何地方被訪問。你可以使用該字典來保存一些信息,這些信息在整個(gè)線程的執(zhí)行過程中都保持不變。比如,你可以使用它來存儲(chǔ)在你的整個(gè)線程過程中Run loop里面多次迭代的狀態(tài)信息。 Cocoa和POSIX以不同的方式保存線程的字典,所以你不能混淆并同時(shí)調(diào)用者兩種技術(shù)。然而只要你在你的線程代碼里面堅(jiān)持使用了其中一種技術(shù),最終的結(jié)果應(yīng)該是一樣的。在Cocoa里面,你使用NSThread的threadDictionary方法來檢索一個(gè)NSMutableDictionary對(duì)象,你可以在它里面添加任何線程需要的鍵。在POSIX里面,你使用pthread_setspecific和pthread_getspecific函數(shù)來設(shè)置和訪問你線程的鍵和值。 1.2.3 設(shè)置線程的脫離狀態(tài)大部分上層的線程技術(shù)都默認(rèn)創(chuàng)建了脫離線程(Datached thread)。大部分情況下,脫離線程(Detached thread)更受歡迎,因?yàn)樗鼈冊(cè)试S系統(tǒng)在線程完成的時(shí)候立即釋放它的數(shù)據(jù)結(jié)構(gòu)。脫離線程同時(shí)不需要顯示的和你的應(yīng)用程序交互。意味著線程檢索的結(jié)果由你來決定。相比之下,系統(tǒng)不回收可連接線程(Joinable thread)的資源直到另一個(gè)線程明確加入該線程,這個(gè)過程可能會(huì)阻止線程執(zhí)行加入。 你可以認(rèn)為可連接線程類似于子線程。雖然你作為獨(dú)立線程運(yùn)行,但是可連接線程在它資源可以被系統(tǒng)回收之前必須被其他線程連接。可連接線程同時(shí)提供了一個(gè)顯示的方式來把數(shù)據(jù)從一個(gè)正在退出的線程傳遞到其他線程。在它退出之前,可連接線程可以傳遞一個(gè)數(shù)據(jù)指針或者其他返回值給pthread_exit函數(shù)。其他線程可以通過pthread_join函數(shù)來拿到這些數(shù)據(jù)。 重要:在應(yīng)用程序退出時(shí),脫離線程可以立即被中斷,而可連接線程則不可以。每個(gè)可連接線程必須在進(jìn)程被允許可以退出的時(shí)候被連接。所以當(dāng)線程處于周期性工作而不允許被中斷的時(shí)候,比如保存數(shù)據(jù)到硬盤,可連接線程是最佳選擇。 如果你想要?jiǎng)?chuàng)建可連接線程,唯一的辦法是使用POSIX線程。POSIX默認(rèn)創(chuàng)建的線程是可連接的。為了把線程標(biāo)記為脫離的或可連接的,使用pthread_attr_setdetachstate函數(shù)來修改正在創(chuàng)建的線程的屬性。在線程啟動(dòng)后,你可以通過調(diào)用pthread_detach函數(shù)來把線程修改為可連接的。關(guān)于更多POSIX線程函數(shù)信息,參與pthread主頁。關(guān)于更多如果連接一個(gè)線程,參閱pthread_join的主頁。 1.2.4 設(shè)置線程的優(yōu)先級(jí)你創(chuàng)建的任何線程默認(rèn)的優(yōu)先級(jí)是和你本身線程相同。內(nèi)核調(diào)度算法在決定該運(yùn)行那個(gè)線程時(shí),把線程的優(yōu)先級(jí)作為考量因素,較高優(yōu)先級(jí)的線程會(huì)比較低優(yōu)先級(jí)的線程具有更多的運(yùn)行機(jī)會(huì)。較高優(yōu)先級(jí)不保證你的線程具體執(zhí)行的時(shí)間,只是相比較低優(yōu)先級(jí)的線程,它更有可能被調(diào)度器選擇執(zhí)行而已。 重要:讓你的線程處于默認(rèn)優(yōu)先級(jí)值是一個(gè)不錯(cuò)的選擇。增加某些線程的優(yōu)先級(jí),同時(shí)有可能增加了某些較低優(yōu)先級(jí)線程的饑餓程度。如果你的應(yīng)用程序包含較高優(yōu)先級(jí)和較低優(yōu)先級(jí)線程,而且它們之間必須交互,那么較低優(yōu)先級(jí)的饑餓狀態(tài)有可能阻塞其他線程,并造成性能瓶頸。 如果你想改變線程的優(yōu)先級(jí),Cocoa和POSIX都提供了一種方法來實(shí)現(xiàn)。對(duì)于Cocoa線程而言,你可以使用NSThread的setThreadPriority:類方法來設(shè)置當(dāng)前運(yùn)行線程的優(yōu)先級(jí)。對(duì)于POSIX線程,你可以使用pthread_setschedparam函數(shù)來實(shí)現(xiàn)。關(guān)于更多信息,參與NSThread Class Reference或pthread_setschedparam主頁。 1.3 編寫你線程的主體入口點(diǎn)對(duì)于大部分而言,Mac OS X上面線程結(jié)構(gòu)的主體入口點(diǎn)和其他平臺(tái)基本一樣。你需要初始化你的數(shù)據(jù)結(jié)構(gòu),做一些工作或可行的設(shè)置一個(gè)run loop,并在線程代碼被執(zhí)行完后清理它。根據(jù)設(shè)計(jì),當(dāng)你寫的主體入口點(diǎn)的時(shí)候有可能需要采取一些額外的步驟。 1.3.1 創(chuàng)建一個(gè)自動(dòng)釋放池(Autorelease Pool)在Objective - C框架鏈接的應(yīng)用程序,通常在它們的每一個(gè)線程必須創(chuàng)建至少一個(gè)自動(dòng)釋放池。如果應(yīng)用程序使用管理模型,即應(yīng)用程序處理的retain和release對(duì)象,那么自動(dòng)釋放池捕獲任何從該線程autorelease的對(duì)象。 如果應(yīng)用程序使用的垃圾回收機(jī)制,而不是管理的內(nèi)存模型,那么創(chuàng)建一個(gè)自動(dòng)釋放池不是絕對(duì)必要的。在垃圾回收的應(yīng)用程序里面,一個(gè)自動(dòng)釋放池是無害的,而且大部分情況是被忽略。允許通過個(gè)代碼管理必須同時(shí)支持垃圾回收和內(nèi)存管理模型。在這種情況下,內(nèi)存管理模型必須支持自動(dòng)釋放池,當(dāng)應(yīng)用程序運(yùn)行垃圾回收的時(shí)候,自動(dòng)釋放池只是被忽略而已。 如果你的應(yīng)用程序使用內(nèi)存管理模型,在你編寫線程主體入口的時(shí)候第一件事情就是創(chuàng)建一個(gè)自動(dòng)釋放池。同樣,在你的線程最后應(yīng)該銷毀該自動(dòng)釋放池。該池保證自動(dòng)釋放。雖然對(duì)象被調(diào)用,但是它們不被release直到線程退出。列表2-2顯示了線程主體入口使用自動(dòng)釋放池的基本結(jié)構(gòu)。 Listing 2-2 Defining your thread entry point routine 因?yàn)楦呒?jí)的自動(dòng)釋放池不會(huì)釋放它的對(duì)象直到線程退出。長(zhǎng)時(shí)運(yùn)行的線程需求新建額外的自動(dòng)釋放池來更頻繁的釋放它的對(duì)象。比如,一個(gè)使用run loop的線程可能在每次運(yùn)行完一次循環(huán)的時(shí)候創(chuàng)建并釋放該自動(dòng)釋放池。更頻繁的釋放對(duì)象可以防止你的應(yīng)用程序內(nèi)存占用太大造成性能問題。雖然對(duì)于任何與性能相關(guān)的行為,你應(yīng)該測(cè)量你代碼的實(shí)際表現(xiàn),并適當(dāng)?shù)卣{(diào)整使用自動(dòng)釋放池。 關(guān)于更多內(nèi)存管理的信息和自動(dòng)釋放池,參閱“內(nèi)存高級(jí)管理編程指南(Advanced Memory Management Programming Guide)”。 1.3.2 設(shè)置異常處理如果你的應(yīng)用程序捕獲并處理異常,那么你的線程代碼應(yīng)該時(shí)刻準(zhǔn)備捕獲任何可能發(fā)生的異常。雖然最好的辦法是在異常發(fā)生的地方捕獲并處理它,但是如果在你的線程里面捕獲一個(gè)拋出的異常失敗的話有可能造成你的應(yīng)用程序強(qiáng)退。在你線程的主體入口點(diǎn)安裝一個(gè)try/catch模塊,可以讓你捕獲任何未知的異常,并提供一個(gè)合適的響應(yīng)。 當(dāng)在Xcode構(gòu)建你項(xiàng)目的時(shí)候,你可以使用C 或者Objective-C的異常處理風(fēng)格。 關(guān)于更多設(shè)置如何在Objective-C里面拋出和捕獲異常的信息,參閱Exception Programming Topics。 1.3.3 設(shè)置一個(gè)Run Loop當(dāng)你想編寫一個(gè)獨(dú)立運(yùn)行的線程時(shí),你有兩種選擇。第一種選擇是寫代碼作為一個(gè)長(zhǎng)期的任務(wù),很少甚至不中斷,線程完成的時(shí)候退出。第二種選擇是把你的線程放入一個(gè)循環(huán)里面,讓它動(dòng)態(tài)的處理到來的任務(wù)請(qǐng)求。第一種方法不需要在你的代碼指定任何東西;你只需要啟動(dòng)的時(shí)候做你打算做的事情即可。然而第二種選擇需要在你的線程里面添加一個(gè)run loop。 Mac OS X和iOS提供了在每個(gè)線程實(shí)現(xiàn)run loop內(nèi)置支持。Cocoa、Carbon和UIKit自動(dòng)在你應(yīng)用程序的主線程啟動(dòng)一個(gè)run loop,但是如果你創(chuàng)建任何輔助線程,你必須手工的設(shè)置一個(gè)run loop并啟動(dòng)它。 關(guān)于更多使用和配置run loop的信息,參閱“Run Loops”部分。 1.4 中斷線程退出一個(gè)線程推薦的方法是讓它在它主體入口點(diǎn)正常退出。經(jīng)管Cocoa、POSIX和Multiprocessing Services提供了直接殺死線程的例程,但是使用這些例程是強(qiáng)烈不鼓勵(lì)的。殺死一個(gè)線程阻止了線程本身的清理工作。線程分配的內(nèi)存可能造成泄露,并且其他線程當(dāng)前使用的資源可能沒有被正確清理干凈,之后造成潛在的問題。 如果你的應(yīng)用程序需要在一個(gè)操作中間中斷一個(gè)線程,你應(yīng)該設(shè)計(jì)你的線程響應(yīng)取消或退出的消息。對(duì)于長(zhǎng)時(shí)運(yùn)行的操作,這意味著周期性停止工作來檢查該消息是否到來。如果該消息的確到來并要求線程退出,那么線程就有機(jī)會(huì)來執(zhí)行任何清理和退出工作;否則,它返回繼續(xù)工作和處理下一個(gè)數(shù)據(jù)塊。 響應(yīng)取消消息的一個(gè)方法是使用run loop的輸入源來接收這些消息。列表2-3顯示了該結(jié)構(gòu)的類似代碼在你的線程的主體入口里面是怎么樣的(該示例顯示了主循環(huán)部分,不包括設(shè)立一個(gè)自動(dòng)釋放池或配置實(shí)際的工作步驟)。該示例在run loop上面安裝了一個(gè)自定義的輸入源,它可以從其他線程接收消息。關(guān)于更多設(shè)置輸入源的信息,參閱“配置Run Loop源”。執(zhí)行工作的總和的一部分后,線程運(yùn)行的run loop來查看是否有消息抵達(dá)輸入源。如果沒有,run loop立即退出,并且循環(huán)繼續(xù)處理下一個(gè)數(shù)據(jù)塊。因?yàn)樵撎幚砥鞑]有直接的訪問exitNow局部變量,退出條件是通過線程的字典來傳輸?shù)摹?/p> Listing 2-3 Checking for an exit condition during a long job
|
|