概述
---html
NSThread類是一個繼承於NSObjct類的輕量級類。一個NSThread對象就表明一個線程。它須要管理線程的生命週期、同步、加鎖等問題,所以會產生必定的性能開銷。
使用NSThread類能夠在特定的線程中被調用某個OC方法。當須要執行一個冗長的任務,而且不想讓這個任務阻塞應用中的其餘部分,尤爲爲了不阻塞app的主線程(由於主線程用於處理用戶界面展現交互和事件相關的操做),這個時候很是適合使用多線程。線程也能夠將一個龐大的任務分爲幾個較小的任務,從而提升多核計算機的性能。objective-c
NSThread類在運行期監聽一個線程的語義和NSOperation類是類似的。好比取消一個線程或者決定一個任務執行完後這個線程是否存在。數組
本文將會從這幾個方面開始探討NSThread安全
方法屬性的介紹
---多線程
初始化(建立)一個NSThread對象app
// 返回一個初始化的NSThread對象 - (instancetype)init // 返回一個帶有多個參數的初始化的NSThread對象 // selector :線程執行的方法,最多隻能接收一個參數 // target :selector消息發送的對象 // argument : 傳給selector的惟一參數,也能夠是nil - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument ); // iOS 10 - (instancetype)initWithBlock:(void (^)(void))block;
啓動一個線程。異步
// 開闢一個新的線程,而且使用特殊的選擇器Selector做爲線程入口,調用完畢後,會立刻建立並開啓新線程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; // iOS 10 + (void)detachNewThreadWithBlock:(void (^)(void))block; // 啓動接受者 - (void)start; // 線程體方法,線程主要入口,start 後執行 // 該方法默認實現了目標(target)和選擇器(selector),用於初始化接受者和調用指定目標(target)的方法。若是子類化NSThread,須要重寫這個方法而且用它來實現這個線程主體。在這種狀況下,是不須要調用super方法的。 // 不該該直接調用這個方法。你應該經過調用啓動方法開啓一個線程。 - (void)main;
使用initWithTarget:selector:
、initWithBlock:
、detachNewThreadSelector:
,detachNewThreadWithBlock:
建立線程都是異步線程。oop
中止一個線程性能
// 阻塞當前線程,直到特定的時間。 + (void)sleepUntilDate:(NSDate *)date; // 讓線程處於休眠狀態,直到通過給定的時間間隔 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 終止當前線程 + (void)exit; // 改變接收者的取消狀態,來表示它應該終止 - (void)cancel;
決定線程狀態.net
// 接收者是否存在 @property (readonly, getter=isExecuting) BOOL executing; // 接收者是否結束執行 @property (readonly, getter=isFinished) BOOL finished; // 接收者是否取消 @property (readonly, getter=isCancelled) BOOL cancelled;
主線程相關
// 當前線程是不是主線程 @property (class, readonly) BOOL isMainThread; // 接受者是不是主線程 @property (readonly) BOOL isMainThread; // 獲取主線程的對象 @property (class, readonly, strong) NSThread *mainThread;
執行環境
// 這個app是不是多線程 + (BOOL)isMultiThreaded; // 返回當前執行線程的線程對象。 @property (class, readonly, strong) NSThread *currentThread; // 返回一個數組,包括回調堆棧返回的地址 @property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses ; // 返回一個數組,包括回調堆棧信號 @property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
線程屬性相關
// 線程對象的字典 @property (readonly, retain) NSMutableDictionary *threadDictionary; NSAssertionHandlerKey // 接收者的名字 @property (nullable, copy) NSString *name; // 接收者的對象大小,以byte爲單位 @property NSUInteger stackSize;
線程優先級
// 線程開啓後是個只讀屬性 @property NSQualityOfService qualityOfService; // 返回當前線程的優先級 + (double)threadPriority; // 接受者的優先級,已經廢棄,使用qualityOfService代替 @property double threadPriority; // 設置當前線程的優先級。設置線程的優先級(0.0 - 1.0,1.0最高級) + (BOOL)setThreadPriority:(double)p;
通知
// 未被實現,沒有實際意義,保留項 NSDidBecomeSingleThreadedNotification // 在線程退出前,一個NSThread對象收到到退出消息時會發送這個通知。 NSThreadWillExitNotification // 當第一個線程啓動時會發送這個通知。這個通知最多發送一次。當NSThread第一次發送用`detachNewThreadSelector:toTarget:withObject:`,`detachNewThreadWithBlock:`,`start`消息時,發送通知。後續調用這些方法是不會發送通知。 NSWillBecomeMultiThreadedNotification
線程間通訊,
在NSObject的分類NSThreadPerformAdditions中的方法(NSThread.h文件中)具備這些特性:
@interface NSObject (NSThreadPerformAdditions) // 若是設置wait爲YES: 等待當前線程執行完之後,主線程纔會執行aSelector方法; // 若是設置wait爲NO:不等待當前線程執行完,就在主線程上執行aSelector方法。 // 若是,當前線程就是主線程,那麼aSelector方法會立刻執行,wait是YES參數無效。 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; // 等於第一個方法中modes是kCFRunLoopCommonModes的狀況。指定了線程中 Runloop 的 Modes = kCFRunLoopCommonModes。 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // 在指定線程上操做,由於子線程默認未添加NSRunloop,在線程未添加runloop時,是不會調用選擇器中的方法的。 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray<NSString *> *)array ; // 等於第一個方法中modes是kCFRunLoopCommonModes的狀況。 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait ; // 隱式建立子線程,在後臺建立。而且是個同步線程。 - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ; @end
// 當前線程操做。 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
NSRunLoop.h文件中
// 延遲操做 /**************** Delayed perform ******************/ @interface NSObject (NSDelayedPerforming) // 異步方法,不會阻塞當前線程,只能在主線程中執行。是把`Selector`加到主隊列裏,當 `delay`以後執行`Selector`。若是主線程在執行業務,那隻能等到執行完全部業務以後纔會去執行`Selector`,就算`delay`等於 0。 // 那`delay `從何時開始計算呢?從發送`performSelector`消息的時候。就算這時主線程在阻塞也會計算時間,當阻塞結束以後,若是到了`delay`那就執行`Selector`,若是沒到就繼續 `delay`。 // 只能在主線程中執行,在子線程中不會調到aSelector方法 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes; // 等於第一個方法中modes是kCFRunLoopCommonModes的狀況。指定了線程中 Runloop 的 Modes = kCFRunLoopCommonModes。 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; // 在方法未到執行時間以前,取消方法。調用這2個方法當前target執行dealloc以前,以確保不會Crash。 + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget; @end // 按照排序順序執行 @interface NSRunLoop (NSOrderedPerform) // 按某種順序order執行方法。參數order越小,優先級越高,執行越早 // selector都是target的方法,argument都是target的參數 // 這2個方法會設置一個定時器去在下個runloop循環的開始時讓target執行aSelector消息。 定時器根據modes確認模式。當定時器觸發,定時器嘗試隊列從runloop中拿出消息並執行。 若是run loop 正在運行,而且是指定modes的一種,則是成功的,不然定時器一直等待直到runloop是modes 中的一種。 - (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes; - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg; - (void)cancelPerformSelectorsWithTarget:(id)target; @end
本文介紹大部分的知識點如思惟導圖:
//1. 手動開啓,action-target 方式 NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil]; [actionTargetThread start]; //2. 手動開啓, block 方式 NSThread *blockThread = [[NSThread alloc] initWithBlock:^{ NSLog(@"%s",__func__); }]; [blockThread start]; //3. 建立就啓動, action-target 方式 [NSThread detachNewThreadSelector:@selector(add2:) toTarget:self withObject:@"detachNewThreadSelector"]; //4. 建立就啓動, block 方式 [NSThread detachNewThreadWithBlock:^{ NSLog(@"%s",__func__); }];
2.1 NSThreadPerformAdditions分類方法,異步調用方法
// 不管在子線程仍是主線程,都會調用主線程方法。
a. 主線程
[self performSelectorOnMainThread:@selector(add:) withObject:nil waitUntilDone:YES]; //[self performSelectorOnMainThread:@selector(add:) withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
子線程默認沒有開啓runloop。須要手動添加,否則選擇器方法沒法調用。
b. 子線程
使用initWithBlock:
方式建立。
//1. 開闢一個子線程 NSThread *subThread1 = [[NSThread alloc] initWithBlock:^{ // 2.子線程方法中添加runloop // 3.實現線程方法 [[NSRunLoop currentRunLoop] run]; }]; //1.2. 啓動一個子線程 [subThread1 start]; // 2. 在子線程中調用方法 // [self performSelector:@selector(add:) onThread:subThread1 withObject:@"22" waitUntilDone:YES]; [self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
使用initWithTarget:selector:object:
建立。
// 1. 開闢一個子線程 NSThread *subThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil]; // 1.2 啓動一個子線程 [subThread2 start]; // 3. 在子線程中調用方法 // [self performSelector:@selector(add:) onThread:subThread2 withObject:@"22" waitUntilDone:YES]; [self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; // 2.子線程方法中添加runloop - (void)startThread{ [[NSRunLoop currentRunLoop] run]; }
c. 後臺線程(隱式建立一個線程)
[self performSelectorInBackground:@selector(add:) withObject:@"arg"];
2.2 協議NSObject方法
建立是的同步任務。
[NSThread detachNewThreadWithBlock:^{ // 直接調用 [self performSelector:@selector(add:) withObject:@"xxx"]; }];
2.3 延遲
NSObject分類NSDelayedPerforming方法,添加異步任務,而且是在主線程上執行。
[self performSelector:@selector(add:) withObject:self afterDelay:2];
2.4 按照順序操做
NSRunLoop分類NSOrderedPerform中的方法
[NSThread detachNewThreadWithBlock:^{ NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop]; // 記得添加端口。否則沒法調用selector方法 [currentRunloop addPort:[NSPort port] forMode:(NSRunLoopMode)kCFRunLoopCommonModes]; [currentRunloop performSelector:@selector(add:) target:self argument:@"arg1" order:1 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; [currentRunloop performSelector:@selector(add:) target:self argument:@"arg3" order:3 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; [currentRunloop run]; }];
問題:
多個線程可能會同時訪問同一塊資源。好比多個線程同時訪問同一個對象、同一個變量、同一個文件等。當多個線程同時搶奪同一個資源,會引發線程不安全性,可能會形成數據錯亂和數據安全問題。
解決:
使用線程同步技術: 能夠對可能會被搶奪的資源,在被被競爭的時候加鎖。讓其保證線程同步狀態。而鎖具備多種類型:好比讀寫鎖、自旋鎖、互斥鎖、信號量、條件鎖等。在NSThread可能形成資源搶奪狀況下,能夠使用互斥鎖。互斥鎖就是多個線程任務按順序的執行。
以下就使用的狀況之一:對須要讀寫操做的資源,進行加鎖操做。
for (NSInteger index = 0 ; index < 100; index ++) { @synchronized (self) { self.allCount -= 5; NSLog(@"%@賣出了車票,還剩%ld",[NSThread currentThread].name,self.allCount); } }
線程的生命週期是:新建 - 就緒 - 運行 - 阻塞 - 死亡。當線程啓動後,它不能一直「霸佔」着CPU獨自運行,因此CPU須要在多條線程之間切換,因而線程狀態也就會隨之改變。
新建和就緒狀態
顯式建立,使用initWithTarget:selector:
和initWithBlock:
建立一個線程,未啓動,只有發送start消息纔會啓動,而後處於就行狀態。
使用detachNewThreadWithBlock:
和detachNewThreadSelector:toTarget:
顯示建立並當即啓動。 還有種建立方式,隱式建立並當即啓動:performSelectorInBackground:withObject:
。
運行和阻塞狀態
若是處於就緒狀態的線程得到了CPU資源,開始執行可執行方法的線程執行體(block或者@Selector),則該線程處於運行狀態。
當發生以下狀況下,線程將會進入阻塞狀態:
sleepUntilDate:
sleepForTimeInterval:
主動放棄所佔用的處理器資源。// 1. 建立:New狀態 NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil]; // 2. 啓動:就緒狀態 [actionTargetThread start]; // 可執行方法 - (void)add:(id)info{ // 3. 執行狀態 NSLog(@"%s,info %@",__func__,info); // 5. 當前線程休眠 [NSThread sleepForTimeInterval:1.0]; NSLog(@"after"); // 4. 程序正常退出 } // 6. 打取消標籤 [actionTargetThread cancel]; // 7. 主動退出 [NSThread exit];
注意: