當我們需要處理一個非常繁重的任務的時候,為了避免阻塞主線程的執行(主線程主要負責用戶交互和相關事件處理),我們需要使用線程。當我們使用線程來把很大的任務劃分成一些小的任務在多核機器上並發的執行的時候,可以大大提高我們程序的性能。NSTread提供給我們了執行線程的管理。
線程是需要內存和性能開銷的,內存開銷包括系統內核內存和應用程序內存。用來管理和協調線程的內核結構存儲在內核。線程的棧空間和每個線程的數據存儲在程序的內存空間。占用內存的這些結構大部分是在線程創建的時候生成和初始化的。因為要和內核交互,所以這個過程是非常耗時的。線程創建大概的開銷如下(其中第二線程的棧空間是可以配置的
):
另外一個開銷就是程序內線程同步的開銷。
使用NSThread創建線程一共有4中方法:
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
[object performSelectorInBackground:@selector(myThreadMainMethod:) withObject:nil];
NSThread* aThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil]; [aThread start];
#import@interface Thread : NSThread @end #import "Thread.h"@implementation Thread - (void)main { @autoreleasepool { //do thread task } } @end//調用 Thread *thread = [[Thread alloc] init]; [thread start];
棧空間是用來存儲為線程創建的本地變量的,棧空間的大小必須在線程的創建之前設定。不能使用創建線程的第一和第二種方法。在調用NSThread的start
方法之前通過setStackSize:
設定新的棧空間大小。
每個線程都維護一個在線程任何地方都能獲取的字典。 我們可以使用NSThread的threadDictionary
方法獲取一個NSMutableDictionary
對象,然後添加我們需要的字段和數據。
通過NSThread創建的線程都是Detached的。如果想要創建joinable線程,只能通過POSIX線程接口創建。
每一個新的線程都有一個默認的優先級。系統的內核調度算法根據線程的優先級來決定線程的執行順序。通常情況下我們不要改變線程的優先級,提高一些線程的優先級可能會導致低優先級的線程一直得不到執行,如果在我們的應用內存在高優先級線程和低優先級線程的交互的話,因為低優先級的線程得不到執行可能阻塞其他線程的執行。這樣會對應用造成性能瓶頸。可以通過NSThread的setThreadPriority:
方法設置線程優先級,優先級為0.0到1.0的double類型,1.0為最高優先級。
在線程的入口處我們需要創建一個Autorelease Pool,當線程退出的時候釋放這個Autorelease Pool。這樣在線程中創建的autorelease對象就可以在線程結束的時候釋放,避免過多的延遲釋放造成程序占用過多的內存。如果是一個長壽命的線程的話,應該創建更多的Autorelease Pool來達到這個目的。例如線程中用到了run loop的時候,每一次的迭代都需要創建Autorelease Pool。在非ARC的情況下創建Autorelease Pool代碼如下:
- (void)myThreadMainRoutine { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool // Do thread work here. [pool release]; // Release the objects in the pool. }
在ARC環境的代碼如下:
- (void)myThreadMainRoutine { @autoreleasepool { //do thread task } }
如果我們的應用能夠捕獲並處理異常,那我們自己創建的線程就應該捕獲異常,如果不這樣的話,當線程出問題的時候,應用就會Crash。
當創建線程的時候我們有兩種選擇,一種是線程執行一個很長的任務然後再任務結束的時候退出。另外一種是線程可以進入一個循環,然後處理動態到達的任務。每一個線程默認都有一個NSRunloop,主線程是默認開啟的,其他線程要手動開啟。關於NSRunloop的理解和使用下一章討論。
終止線程最好不要用POSIX接口直接殺死線程,這種粗暴的方法會導致系統無法回收線程使用的資源,造成內存洩露,還有可能對程序的運行造成影響。終止線程最好的方式是能夠讓線程接收取消和退出消息,這樣線程在受到消息的時候就有機會清理已持有的資源,避免內存洩露。這種方案的一種實現方式是使用NSRunloop的input source來接收消息,每一次的NSRunloop循環都檢查退出條件是否為YES,如果為YES退出循環回收資源,如果為NO,則進入下一次NSRunloop循環。例子代碼如下:
- (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 is not waiting to fire. [runLoop runUntilDate:[NSDate date]]; // Check to see if an input source handler changed the exitNow value. exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue]; } }參考鏈接: 1.Threading Programming Guide 2.Concurrency Programming Guide