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