一、什么是NSThread
NSThread是基于線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。
二、NSThread方法介紹
1)動態創建
| 1 | NSThread * newThread = [[NSThread alloc]initWithTarget:self selector:@selector(threadRun) object:nil]; | 
動態方法返回一個新的thread對象,需要調用start方法來啟動線程
2)靜態創建
| 1 | [NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil]; | 
由于靜態方法沒有返回值,如果需要獲取新創建的thread,需要在selector中調用獲取當前線程的方法
3)線程開啟
| 1 | [newThread start]; | 
4)線程暫停
| 12 | [NSThread sleepForTimeInterval:1.0]; (以暫停一秒為例)[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; | 
NSThread的暫停會有阻塞當前線程的效果
5)線程取消
| 1 | [newThread cancel]; | 
取消線程并不會馬上停止并退出線程,僅僅只作(線程是否需要退出)狀態記錄
6)線程停止
| 1 | [NSThread exit]; | 
停止方法會立即終止除主線程以外所有線程(無論是否在執行任務)并退出,需要在掌控所有線程狀態的情況下調用此方法,否則可能會導致內存問題。
7)獲取當前線程
| 1 | [NSThread currentThread]; | 
8)獲取主線程
| 1 | [NSThread mainThread]; | 
9)線程優先級設置
iOS 8以前使用
| 1 | [NSThread setThreadPRiority:1.0]; | 
這個方法的優先級的數值設置讓人困惑,因為你不知道你應該設置多大的值是比較合適的,因此在iOS8之后,threadPriority添加了一句注釋:To be deprecated; use qualityOfService below
意思就是iOS 8以后推薦使用qualityOfService屬性,通過量化的優先級枚舉值來設置
qualityOfService的枚舉值如下:
NSQualityOfServiceUserInteractive:最高優先級,用于用戶交互事件
NSQualityOfServiceUserInitiated:次高優先級,用于用戶需要馬上執行的事件
NSQualityOfServiceDefault:默認優先級,主線程和沒有設置優先級的線程都默認為這個優先級
NSQualityOfServiceUtility:普通優先級,用于普通任務
NSQualityOfServiceBackground:最低優先級,用于不重要的任務
比如給線程設置次高優先級:
| 1 | [newThread setQualityOfService:NSQualityOfServiceUserInitiated]; | 
三、線程間通信
常用的有三種:
1、指定當前線程執行操作
| 123 | [self performSelector:@selector(threadRun)];[self performSelector:@selector(threadRun) withObject:nil];[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0]; | 
2、(在其他線程中)指定主線程執行操作
| 1 | [self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES]; | 
注意:更新UI要在主線程中進行
3、(在主線程中)指定其他線程執行操作
| 12 | [self performSelector:@selector(threadRun) onThread:newThread withObject:nil waitUntilDone:YES]; //這里指定為某個線程[self performSelectorInBackground:@selector(threadRun) withObject:nil];//這里指定為后臺線程 | 
四、線程同步
線程和其他線程可能會共享一些資源,當多個線程同時讀寫同一份共享資源的時候,可能會引起沖突。線程同步是指是指在一定的時間內只允許某一個線程訪問某個資源
iOS實現線程加鎖有NSLock和@synchronized兩種方式。
五、線程的創建和使用實例:模擬售票
情景:某演唱會門票發售,在廣州和北京均開設窗口進行銷售,以下是代碼實現
先監聽線程退出的通知,以便知道線程什么時候退出
| 1 | [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil]; | 
設置演唱會的門票數量
| 1 | _ticketCount = 50; | 
新建兩個子線程(代表兩個窗口同時銷售門票)
| 123456789101112131415161718192021 | NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];window1.name = @"北京售票窗口";[window1 start];NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];window2.name = @"廣州售票窗口";[window2 start];線程啟動后,執行saleTicket,執行完畢后就會退出,為了模擬持續售票的過程,我們需要給它加一個循環- (void)saleTicket {    while(1) {    //如果還有票,繼續售賣        if(_ticketCount > 0) {        _ticketCount --;        NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]);        [NSThread sleepForTimeInterval:0.2];    }    //如果已賣完,關閉售票窗口    else{        break;    }}} | 
執行結果:
| 123456789101112131415 | 2016-04-06 19:25:36.637 MutiThread[4705:1371666] 剩余票數:9 窗口:廣州售票窗口2016-04-06 19:25:36.637 MutiThread[4705:1371665] 剩余票數:8 窗口:北京售票窗口2016-04-06 19:25:36.839 MutiThread[4705:1371666] 剩余票數:7 窗口:廣州售票窗口2016-04-06 19:25:36.839 MutiThread[4705:1371665] 剩余票數:7 窗口:北京售票窗口2016-04-06 19:25:37.045 MutiThread[4705:1371666] 剩余票數:5 窗口:廣州售票窗口2016-04-06 19:25:37.045 MutiThread[4705:1371665] 剩余票數:6 窗口:北京售票窗口2016-04-06 19:25:37.250 MutiThread[4705:1371665] 剩余票數:4 窗口:北京售票窗口2016-04-06 19:25:37.250 MutiThread[4705:1371666] 剩余票數:4 窗口:廣州售票窗口2016-04-06 19:25:37.456 MutiThread[4705:1371666] 剩余票數:2 窗口:廣州售票窗口2016-04-06 19:25:37.456 MutiThread[4705:1371665] 剩余票數:3 窗口:北京售票窗口2016-04-06 19:25:37.661 MutiThread[4705:1371665] 剩余票數:1 窗口:北京售票窗口2016-04-06 19:25:37.661 MutiThread[4705:1371666] 剩余票數:1 窗口:廣州售票窗口2016-04-06 19:25:37.866 MutiThread[4705:1371665] 剩余票數:0 窗口:北京售票窗口2016-04-06 19:25:37.867 MutiThread[4705:1371666] <nsthread: 0x7fdc91e289f0>{number = 3, name = 廣州售票窗口} Will Exit2016-04-06 19:25:38.070 MutiThread[4705:1371665] <nsthread: 0x7fdc91e24d60>{number = 2, name = 北京售票窗口} Will Exit</nsthread: 0x7fdc91e24d60></nsthread: 0x7fdc91e289f0> | 
可以看到,票的銷售過程中出現了剩余數量錯亂的情況,這就是前面提到的線程同步問題。
售票是一個典型的需要線程同步的場景,由于售票渠道有很多,而票的資源是有限的,當多個渠道在短時間內賣出大量的票的時候,如果沒有同步機制來管理票的數量,將會導致票的總數和售出票數對應不上的錯誤。
我們在售票的過程中給票加上同步鎖:同一時間內,只有一個線程能對票的數量進行操作,當操作完成之后,其他線程才能繼續對票的數量進行操作。
| 12345678910111213141516 | - (void)saleTicket {    while(1) {    @synchronized(self) {        //如果還有票,繼續售賣        if(_ticketCount > 0) {        _ticketCount --;        NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]);        [NSThread sleepForTimeInterval:0.2];        }        //如果已賣完,關閉售票窗口        else{                break;            }        }    }} | 
運行結果:
| 1234567891011121314 | 2016-04-06 19:31:27.913 MutiThread[4718:1406865] 剩余票數:11 窗口:北京售票窗口2016-04-06 19:31:28.115 MutiThread[4718:1406866] 剩余票數:10 窗口:廣州售票窗口2016-04-06 19:31:28.317 MutiThread[4718:1406865] 剩余票數:9 窗口:北京售票窗口2016-04-06 19:31:28.522 MutiThread[4718:1406866] 剩余票數:8 窗口:廣州售票窗口2016-04-06 19:31:28.728 MutiThread[4718:1406865] 剩余票數:7 窗口:北京售票窗口2016-04-06 19:31:28.929 MutiThread[4718:1406866] 剩余票數:6 窗口:廣州售票窗口2016-04-06 19:31:29.134 MutiThread[4718:1406865] 剩余票數:5 窗口:北京售票窗口2016-04-06 19:31:29.339 MutiThread[4718:1406866] 剩余票數:4 窗口:廣州售票窗口2016-04-06 19:31:29.545 MutiThread[4718:1406865] 剩余票數:3 窗口:北京售票窗口2016-04-06 19:31:29.751 MutiThread[4718:1406866] 剩余票數:2 窗口:廣州售票窗口2016-04-06 19:31:29.952 MutiThread[4718:1406865] 剩余票數:1 窗口:北京售票窗口2016-04-06 19:31:30.158 MutiThread[4718:1406866] 剩余票數:0 窗口:廣州售票窗口2016-04-06 19:31:30.363 MutiThread[4718:1406866] <nsthread: 0x7ff0c1637320>{number = 3, name = 廣州售票窗口} Will Exit2016-04-06 19:31:30.363 MutiThread[4718:1406865] <nsthread: 0x7ff0c1420cb0>{number = 2, name = 北京售票窗口} Will Exit</nsthread: 0x7ff0c1420cb0></nsthread: 0x7ff0c1637320> | 
可以看到,票的數量沒有出現錯亂的情況。
線程的持續運行和退出
我們注意到,線程啟動后,執行saleTicket完畢后就馬上退出了,怎樣能讓線程一直運行呢(窗口一直開放,可以隨時指派其賣演唱會的門票的任務),答案就是給線程加上runLoop
| 12 | //先監聽線程退出的通知,以便知道線程什么時候退出[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil]; | 
| 12 | //設置演唱會的門票數量_ticketCount = 50; | 
新建兩個子線程(代表兩個窗口同時銷售門票)
| 1234 | NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(thread1) object:nil];[window1 start];NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(thread2) object:nil];[window2 start]; | 
接著我們給線程創建一個runLoop
| 12345678910 | - (void)thread1 {    [NSThread currentThread].name = @"北京售票窗口";    NSRunLoop * runLoop1 = [NSRunLoop currentRunLoop];    [runLoop1 runUntilDate:[NSDate date]]; //一直運行}- (void)thread2 {    [NSThread currentThread].name = @"廣州售票窗口";    NSRunLoop * runLoop2 = [NSRunLoop currentRunLoop];    [runLoop2 runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //自定義運行時間} | 
然后就可以指派任務給線程了,這里我們讓兩個線程都執行相同的任務(售票)
| 12 | [self performSelector:@selector(saleTicket) onThread:window1 withObject:nil waitUntilDone:NO];[self performSelector:@selector(saleTicket) onThread:window2 withObject:nil waitUntilDone:NO]; | 
運行結果:
| 12345678910111213 | 2016-04-06 19:43:22.585 MutiThread[4762:1478200] 剩余票數:11 窗口:北京售票窗口2016-04-06 19:43:22.788 MutiThread[4762:1478201] 剩余票數:10 窗口:廣州售票窗口2016-04-06 19:43:22.993 MutiThread[4762:1478200] 剩余票數:9 窗口:北京售票窗口2016-04-06 19:43:23.198 MutiThread[4762:1478201] 剩余票數:8 窗口:廣州售票窗口2016-04-06 19:43:23.404 MutiThread[4762:1478200] 剩余票數:7 窗口:北京售票窗口2016-04-06 19:43:23.609 MutiThread[4762:1478201] 剩余票數:6 窗口:廣州售票窗口2016-04-06 19:43:23.810 MutiThread[4762:1478200] 剩余票數:5 窗口:北京售票窗口2016-04-06 19:43:24.011 MutiThread[4762:1478201] 剩余票數:4 窗口:廣州售票窗口2016-04-06 19:43:24.216 MutiThread[4762:1478200] 剩余票數:3 窗口:北京售票窗口2016-04-06 19:43:24.422 MutiThread[4762:1478201] 剩余票數:2 窗口:廣州售票窗口2016-04-06 19:43:24.628 MutiThread[4762:1478200] 剩余票數:1 窗口:北京售票窗口2016-04-06 19:43:24.833 MutiThread[4762:1478201] 剩余票數:0 窗口:廣州售票窗口2016-04-06 19:43:25.039 MutiThread[4762:1478201] <nsthread: 0x7fe0d3c24360>{number = 3, name = 廣州售票窗口} Will Exit</nsthread: 0x7fe0d3c24360> | 
可以看到,當票賣完后,兩個線程并沒有退出,仍在繼續運行,當到達指定時間后,線程2退出了,如果需要讓線程1退出,需要我們手動管理。
比如我們讓線程完成任務(售票)后自行退出,可以這樣操作
| 123456789101112131415161718192021222324 | - (void)saleTicket {    while(1) {        @synchronized(self) {        //如果還有票,繼續售賣        if(_ticketCount > 0) {        _ticketCount --;            NSLog(@"%@", [NSString stringWithFormat:@"剩余票數:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]);            [NSThread sleepForTimeInterval:0.2];        }        //如果已賣完,關閉售票窗口        else{            if([NSThread currentThread].isCancelled) {            break;        }else{            NSLog(@"售賣完畢");            //給當前線程標記為取消狀態            [[NSThread currentThread] cancel];            //停止當前線程的runLoop            CFRunLoopStop(CFRunLoopGetCurrent());            }        }      }    }} | 
運行結果:
| 123456789101112131415 | 2016-04-06 20:08:38.287 MutiThread[4927:1577193] 剩余票數:10 窗口:北京售票窗口2016-04-06 20:08:38.489 MutiThread[4927:1577194] 剩余票數:9 窗口:廣州售票窗口2016-04-06 20:08:38.690 MutiThread[4927:1577193] 剩余票數:8 窗口:北京售票窗口2016-04-06 20:08:38.892 MutiThread[4927:1577194] 剩余票數:7 窗口:廣州售票窗口2016-04-06 20:08:39.094 MutiThread[4927:1577193] 剩余票數:6 窗口:北京售票窗口2016-04-06 20:08:39.294 MutiThread[4927:1577194] 剩余票數:5 窗口:廣州售票窗口2016-04-06 20:08:39.499 MutiThread[4927:1577193] 剩余票數:4 窗口:北京售票窗口2016-04-06 20:08:39.700 MutiThread[4927:1577194] 剩余票數:3 窗口:廣州售票窗口2016-04-06 20:08:39.905 MutiThread[4927:1577193] 剩余票數:2 窗口:北京售票窗口2016-04-06 20:08:40.106 MutiThread[4927:1577194] 剩余票數:1 窗口:廣州售票窗口2016-04-06 20:08:40.312 MutiThread[4927:1577193] 剩余票數:0 窗口:北京售票窗口2016-04-06 20:08:40.516 MutiThread[4927:1577194] 售賣完畢2016-04-06 20:08:40.516 MutiThread[4927:1577193] 售賣完畢2016-04-06 20:08:40.517 MutiThread[4927:1577193] <nsthread: 0x7fb719d54000>{number = 2, name = 北京售票窗口} Will Exit2016-04-06 20:08:40.517 MutiThread[4927:1577194] <nsthread: 0x7fb719d552f0>{number = 3, name = 廣州售票窗口} Will Exit</nsthread: 0x7fb719d552f0></nsthread: 0x7fb719d54000> | 
如果確定兩個線程都是isCancelled狀態,可以調用[NSThread exit]方法來終止線程。
新聞熱點
疑難解答