iOS多線程編程指南(二)線程管理
線程管理
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)用程序里面使用它們的例子。
注意:獲取關(guān)于Mac OS上面線程架構(gòu),或者更多關(guān)于線程的背景資料。請(qǐng)參閱技術(shù)說明TN2028 –“線程架構(gòu)”。
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í)線程所需的大致成本。這些成本里面的部分是可配置的,比如為輔助線程分配堆??臻g的大小。創(chuàng)建一個(gè)線程所需的時(shí)間成本是粗略估計(jì)的,僅用于當(dāng)互相比較的時(shí)候。線程創(chuàng)建時(shí)間很大程度依賴于處理器的負(fù)載,計(jì)算速度,和可用的系統(tǒng)和程序空間。
Table 2-1 Thread creation costs
Item |
Approximate cost |
Notes |
Kernel data structures |
Approximately 1 KB |
This memory is used to store the thread data structures and attributes, much of which is allocated as wired memory and therefore cannot be paged to disk. |
Stack space |
512 KB (secondary threads) 8 MB (Mac OS X main thread) 1 MB (iOS main thread) |
The minimum allowed stack size for secondary threads is 16 KB and the stack size must be a multiple of 4 KB. The space for this memory is set aside in your process space at thread creation time, but the actual pages associated with that memory are not created until they are needed. |
Creation time |
Approximately 90 microseconds |
This value reflects the time between the initial call to create the thread and the time at which the thread’s entry point routine began executing. The figures were determined by analyzing the mean and median values generated during thread creation on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5. |
注意:因?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í)間在鎖的等待而沒有做任何事情。
2.創(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)于更多如何配置你的線程的信息,參閱“線程屬性配置”部分。
2.1 使用NSThread
使用NSThread來創(chuàng)建線程有兩個(gè)可以的方法:
使用detachNewThreadSelector:toTarget:withObject:類方法來生成一個(gè)新的線程。
創(chuàng)建一個(gè)新的NSThread對(duì)象,并調(diào)用它的start方法。(僅在iOS和Mac OS X v10.5及其之后才支持)
這兩種創(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è)線程。
- [NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
在Mac OS X v10.5之前,你使用NSThread類來生成多線程。雖然你可以獲取一個(gè)NSThread對(duì)象并訪問線程的屬性,但你只能在線程運(yùn)行之后在其內(nèi)部做到這些。在Mac OS X v10.5支持創(chuàng)建一個(gè)NSThread對(duì)象,而無需立即生成一個(gè)相應(yīng)的新線程(這些在iOS里面同樣可用)。新版支持使得在線程啟動(dòng)之前獲取并設(shè)置線程的很多屬性成為可能。這也讓用線程對(duì)象來引用正在運(yùn)行的線程成為可能。
在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方法,如下面代碼:
- NSThread* myThread = [[NSThread alloc] initWithTarget:self
- selector:@selector(myThreadMainMethod:)
- object:nil];
- [myThread start]; // Actually create the thread
注意:使用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)”部分。
2.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
- #include <assert.h>
- #include <pthread.h>
- void* PosixThreadMainRoutine(void* data)
- {
- // Do some work here.
- return NULL;
- }
- void LaunchThread()
- {
- // Create the thread using POSIX routines.
- pthread_attr_t attr;
- pthread_t posixThreadID;
- int returnVal;
- returnVal = pthread_attr_init(&attr);
- assert(!returnVal);
- returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- assert(!returnVal);
- int threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL);
- returnVal = pthread_attr_destroy(&attr);
- assert(!returnVal);
- if (threadError != 0)
- {
- // Report an error.
- }
- }
為了在新建的線程里面和你應(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的主頁。
2.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è)新的線程:
- [myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
調(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)于更是介紹如果配置線程的信息,參閱“配置線程屬性”部分。
2.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)。
別走開,下頁內(nèi)容更精彩
#p#
2.5 在Cocoa程序上面使用POSIX線程
經(jīng)管NSThread類是Cocoa應(yīng)用程序里面創(chuàng)建多線程的主要接口,如果可以更方便的話你可以任意使用POSIX線程帶替代。例如,如果你的代碼里面已經(jīng)使用了它,而你又不想改寫它的話,這時(shí)你可能需要使用POSIX多線程。如果你真打算在Cocoa程序里面使用POSIX線程,你應(yīng)該了解如果在Cocoa和線程間交互,并遵循以下部分的一些指南。
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)一下。
混合POSIX和Cocoa的鎖
在同一個(gè)應(yīng)用程序里面混合使用POSIX和Cocoa的鎖很安全。Cocoa鎖和條件對(duì)象基本上只是封裝了POSIX的互斥體和條件。然而給定一個(gè)鎖,你必須總是使用同樣的接口來創(chuàng)建和操縱該鎖。換言之,你不能使用Cocoa的NSLock對(duì)象來操縱一個(gè)你使用pthread_mutex_init函數(shù)生成的互斥體,反之亦然。
3.配置線程屬性
創(chuàng)建線程之后,或者有時(shí)候是之前,你可能需要配置不同的線程環(huán)境。以下部分描述了一些你可以做的改變,和在什么時(shí)候你需要做這些改變。
3.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
Technology |
Option |
Cocoa |
In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the start method of the thread object, use thesetStackSize: method to specify the new stack size. |
POSIX |
Create a new pthread_attr_t structure and use the pthread_attr_setstacksize function to change the default stack size. Pass the attributes to the pthread_create function when creating your thread. |
Multiprocessing Services |
Pass the appropriate stack size value to the MPCreateTask function when you create your thread. |
3.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è)置和訪問你線程的鍵和值。
3.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的主頁。
3.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主頁。
4.編寫你線程的主體入口點(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í)候有可能需要采取一些額外的步驟。
4.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
- - (void)myThreadMainRoutine
- {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
- // Do thread work here.
- [pool release]; // Release the objects in the pool.
- }
關(guān)于更多內(nèi)存管理的信息和自動(dòng)釋放池,參閱“內(nèi)存高級(jí)管理編程指南(Advanced Memory Management Programming Guide)”。
4.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。
4.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”部分。
5.中斷線程
退出一個(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
- - (void)threadMainRoutine
- {
- BOOL moreWorkToDo = YES;
- BOOL exitNow = NO;
- NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
- // Add the exitNow BOOL to the thread dictionary.
- NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
- [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
- // Install an input source.
- [self myInstallCustomInputSource];
- while (moreWorkToDo && !exitNow)
- {
- // Do one chunk of a larger body of work here.
- // Change the value of the moreWorkToDo Boolean when done.
- // Run the run loop but timeout immediately if the input source isn't waiting to fire.
- [runLoop runUntilDate:[NSDate date]];
- // Check to see if an input source handler changed the exitNow value.
- exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
- }
- }