NSThread是基於線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。
1)動態創建
NSThread*newThread=[[NSThreadalloc]initWithTarget:self
selector:@selector(threadRun)object:nil];
動態方法返回一個新的thread對象,需要調用start方法來啟動線程
2)靜態創建
1[NSThreaddetachNewThreadSelector:@selector(threadRun)toTarget:selfwithObject:nil];
由於靜態方法沒有返回值,如果需要獲取新創建的thread,需要在selector中調用獲取當前線程的方法
3)線程開啟
1[newThreadstart];
4)線程暫停
[NSThreadsleepForTimeInterval:1.0]; (以暫停一秒為例) [NSThreadsleepUntilDate:[NSDatedateWithTimeIntervalSinceNow:1.0]];
NSThread的暫停會有阻塞當前線程的效果
5)線程取消
[newThreadcancel];
取消線程並不會馬上停止並退出線程,僅僅只作(線程是否需要退出)狀態記錄
6)線程停止
[NSThreadexit];
停止方法會立即終止除主線程以外所有線程(無論是否在執行任務)並退出,需要在掌控所有線程狀態的情況下調用此方法,
否則可能會導致內存問題。
7)獲取當前線程
[NSThreadcurrentThread];
8)獲取主線程
[NSThreadmainThread];
9)線程優先級設置
iOS 8以前使用
[NSThreadsetThreadPriority:1.0];
這個方法的優先級的數值設置讓人困惑,因為你不知道你應該設置多大的值是比較合適的,因此在iOS8之後,threadPriority添加了
一句注釋:To be deprecated; use qualityOfService below
意思就是iOS 8以後推薦使用qualityOfService屬性,通過量化的優先級枚舉值來設置
qualityOfService的枚舉值如下:
NSQualityOfServiceUserInteractive:最高優先級,用於用戶交互事件
NSQualityOfServiceUserInitiated:次高優先級,用於用戶需要馬上執行的事件
NSQualityOfServiceDefault:默認優先級,主線程和沒有設置優先級的線程都默認為這個優先級
NSQualityOfServiceUtility:普通優先級,用於普通任務
NSQualityOfServiceBackground:最低優先級,用於不重要的任務
比如給線程設置次高優先級:
[newThreadsetQualityOfService:NSQualityOfServiceUserInitiated];
常用的有三種:
1、指定當前線程執行操作
[selfperformSelector:@selector(threadRun)]; [selfperformSelector:@selector(threadRun)withObject:nil]; [selfperformSelector:@selector(threadRun)withObject:nilafterDelay:2.0];
2、(在其他線程中)指定主線程執行操作
[selfperformSelectorOnMainThread:@selector(threadRun)withObject:nil
waitUntilDone:YES];
注意:更新UI要在主線程中進行
3、(在主線程中)指定其他線程執行操作
[selfperformSelector:@selector(threadRun)onThread:newThread
withObject:nilwaitUntilDone:YES];
//這裡指定為某個線程 [selfperformSelectorInBackground:@selector(threadRun)withObject:nil];
//這裡指定為後台線程
線程和其他線程可能會共享一些資源,當多個線程同時讀寫同一份共享資源的時候,可能會引起沖突。線程同步是指是指在一定的時間內只允許
某一個線程訪問某個資源
iOS實現線程加鎖有NSLock和@synchronized兩種方式。
情景:某演唱會門票發售,在廣州和北京均開設窗口進行銷售,以下是代碼實現
先監聽線程退出的通知,以便知道線程什麼時候退出
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(threadExitNotice)
name:NSThreadWillExitNotificationobject:nil];
設置演唱會的門票數量
_ticketCount=50;
新建兩個子線程(代表兩個窗口同時銷售門票)
NSThread*window1=[[NSThreadalloc]initWithTarget:self
selector:@selector(saleTicket)object:nil]; window1.name=@"北京售票窗口"; [window1start]; NSThread*window2=[[NSThreadalloc]initWithTarget:self
selector:@selector(saleTicket)object:nil]; window2.name=@"廣州售票窗口"; [window2start]; 線程啟動後,執行saleTicket,執行完畢後就會退出,為了模擬持續售票的過程,
我們需要給它加一個循環 -(void)saleTicket{ while(1){ //如果還有票,繼續售賣 if(_ticketCount>0){ _ticketCount--; NSLog(@"%@",[NSStringstringWithFormat:@"剩余票數:%ld窗口:%@",_ticketCount,[NSThreadcurrentThread].name]); [NSThreadsleepForTimeInterval:0.2]; } //如果已賣完,關閉售票窗口 else{ break; } } }
執行結果:
2016-04-0619:25:36.637MutiThread[4705:1371666]剩余票數:9窗口:廣州售票窗口 2016-04-0619:25:36.637MutiThread[4705:1371665]剩余票數:8窗口:北京售票窗口 2016-04-0619:25:36.839MutiThread[4705:1371666]剩余票數:7窗口:廣州售票窗口 2016-04-0619:25:36.839MutiThread[4705:1371665]剩余票數:7窗口:北京售票窗口 2016-04-0619:25:37.045MutiThread[4705:1371666]剩余票數:5窗口:廣州售票窗口 2016-04-0619:25:37.045MutiThread[4705:1371665]剩余票數:6窗口:北京售票窗口 2016-04-0619:25:37.250MutiThread[4705:1371665]剩余票數:4窗口:北京售票窗口 2016-04-0619:25:37.250MutiThread[4705:1371666]剩余票數:4窗口:廣州售票窗口 2016-04-0619:25:37.456MutiThread[4705:1371666]剩余票數:2窗口:廣州售票窗口 2016-04-0619:25:37.456MutiThread[4705:1371665]剩余票數:3窗口:北京售票窗口 2016-04-0619:25:37.661MutiThread[4705:1371665]剩余票數:1窗口:北京售票窗口 2016-04-0619:25:37.661MutiThread[4705:1371666]剩余票數:1窗口:廣州售票窗口 2016-04-0619:25:37.866MutiThread[4705:1371665]剩余票數:0窗口:北京售票窗口 2016-04-0619:25:37.867MutiThread[4705:1371666]
{number=3,name=廣州售票窗口}WillExit 2016-04-0619:25:38.070MutiThread[4705:1371665]
{number=2,name=北京售票窗口}WillExit
可以看到,票的銷售過程中出現了剩余數量錯亂的情況,這就是前面提到的線程同步問題。
售票是一個典型的需要線程同步的場景,由於售票渠道有很多,而票的資源是有限的,當多個渠道在短時間內賣出大量
的票的時候,如果沒有同步機制來管理票的數量,將會導致票的總數和售出票數對應不上的錯誤。
我們在售票的過程中給票加上同步鎖:同一時間內,只有一個線程能對票的數量進行操作,當操作完成之後,其他線程
才能繼續對票的數量進行操作。
-(void)saleTicket{ while(1){ @synchronized(self){ //如果還有票,繼續售賣 if(_ticketCount>0){ _ticketCount--; NSLog(@"%@",[NSStringstringWithFormat:@"剩余票數:%ld窗口:%@",_ticketCount,[NSThreadcurrentThread].name]); [NSThreadsleepForTimeInterval:0.2]; } //如果已賣完,關閉售票窗口 else{ break; } } } }
運行結果:
2016-04-0619:31:27.913MutiThread[4718:1406865]剩余票數:11窗口:北京售票窗口 2016-04-0619:31:28.115MutiThread[4718:1406866]剩余票數:10窗口:廣州售票窗口 2016-04-0619:31:28.317MutiThread[4718:1406865]剩余票數:9窗口:北京售票窗口 2016-04-0619:31:28.522MutiThread[4718:1406866]剩余票數:8窗口:廣州售票窗口 2016-04-0619:31:28.728MutiThread[4718:1406865]剩余票數:7窗口:北京售票窗口 2016-04-0619:31:28.929MutiThread[4718:1406866]剩余票數:6窗口:廣州售票窗口 2016-04-0619:31:29.134MutiThread[4718:1406865]剩余票數:5窗口:北京售票窗口 2016-04-0619:31:29.339MutiThread[4718:1406866]剩余票數:4窗口:廣州售票窗口 2016-04-0619:31:29.545MutiThread[4718:1406865]剩余票數:3窗口:北京售票窗口 2016-04-0619:31:29.751MutiThread[4718:1406866]剩余票數:2窗口:廣州售票窗口 2016-04-0619:31:29.952MutiThread[4718:1406865]剩余票數:1窗口:北京售票窗口 2016-04-0619:31:30.158MutiThread[4718:1406866]剩余票數:0窗口:廣州售票窗口 2016-04-0619:31:30.363MutiThread[4718:1406866]
{number=3,name=廣州售票窗口}WillExit 2016-04-0619:31:30.363MutiThread[4718:1406865]
{number=2,name=北京售票窗口}WillExit
可以看到,票的數量沒有出現錯亂的情況。
線程的持續運行和退出
我們注意到,線程啟動後,執行saleTicket完畢後就馬上退出了,怎樣能讓線程一直運行呢(窗口一直開放,
可以隨時指派其賣演唱會的門票的任務),答案就是給線程加上runLoop
//先監聽線程退出的通知,以便知道線程什麼時候退出 [[NSNotificationCenterdefaultCenter]addObserver:self
selector:@selector(threadExitNotice)
name:NSThreadWillExitNotificationobject:nil];
1 2//設置演唱會的門票數量 _ticketCount=50;
新建兩個子線程(代表兩個窗口同時銷售門票)
NSThread*window1=[[NSThreadalloc]initWithTarget:self
selector:@selector(thread1)object:nil]; [window1start]; NSThread*window2=[[NSThreadalloc]initWithTarget:self
selector:@selector(thread2)object:nil]; [window2start];
接著我們給線程創建一個runLoop
-(void)thread1{ [NSThreadcurrentThread].name=@"北京售票窗口"; NSRunLoop*runLoop1=[NSRunLoopcurrentRunLoop]; [runLoop1runUntilDate:[NSDatedate]];//一直運行 } -(void)thread2{ [NSThreadcurrentThread].name=@"廣州售票窗口"; NSRunLoop*runLoop2=[NSRunLoopcurrentRunLoop]; [runLoop2runMode:NSDefaultRunLoopModebeforeDate:[NSDatedateWithTimeIntervalSinceNow:10.0]];//自定義運行時間 }
然後就可以指派任務給線程了,這裡我們讓兩個線程都執行相同的任務(售票)
[selfperformSelector:@selector(saleTicket)onThread:window1
withObject:nilwaitUntilDone:NO]; [selfperformSelector:@selector(saleTicket)onThread:window2
withObject:nilwaitUntilDone:NO];
運行結果:
2016-04-0619:43:22.585MutiThread[4762:1478200]剩余票數:11窗口:北京售票窗口 2016-04-0619:43:22.788MutiThread[4762:1478201]剩余票數:10窗口:廣州售票窗口 2016-04-0619:43:22.993MutiThread[4762:1478200]剩余票數:9窗口:北京售票窗口 2016-04-0619:43:23.198MutiThread[4762:1478201]剩余票數:8窗口:廣州售票窗口 2016-04-0619:43:23.404MutiThread[4762:1478200]剩余票數:7窗口:北京售票窗口 2016-04-0619:43:23.609MutiThread[4762:1478201]剩余票數:6窗口:廣州售票窗口 2016-04-0619:43:23.810MutiThread[4762:1478200]剩余票數:5窗口:北京售票窗口 2016-04-0619:43:24.011MutiThread[4762:1478201]剩余票數:4窗口:廣州售票窗口 2016-04-0619:43:24.216MutiThread[4762:1478200]剩余票數:3窗口:北京售票窗口 2016-04-0619:43:24.422MutiThread[4762:1478201]剩余票數:2窗口:廣州售票窗口 2016-04-0619:43:24.628MutiThread[4762:1478200]剩余票數:1窗口:北京售票窗口 2016-04-0619:43:24.833MutiThread[4762:1478201]剩余票數:0窗口:廣州售票窗口 2016-04-0619:43:25.039MutiThread[4762:1478201]
{number=3,name=廣州售票窗口}
WillExit
可以看到,當票賣完後,兩個線程並沒有退出,仍在繼續運行,當到達指定時間後,線程2退出了,
如果需要讓線程1退出,需要我們手動管理。
比如我們讓線程完成任務(售票)後自行退出,可以這樣操作
-(void)saleTicket{ while(1){ @synchronized(self){ //如果還有票,繼續售賣 if(_ticketCount>0){ _ticketCount--; NSLog(@"%@",[NSStringstringWithFormat:@"剩余票數:%ld窗口:%@",_ticketCount,[NSThreadcurrentThread].name]); [NSThreadsleepForTimeInterval:0.2]; } //如果已賣完,關閉售票窗口 else{ if([NSThreadcurrentThread].isCancelled){ break; }else{ NSLog(@"售賣完畢"); //給當前線程標記為取消狀態 [[NSThreadcurrentThread]cancel]; //停止當前線程的runLoop CFRunLoopStop(CFRunLoopGetCurrent()); } } } } }
運行結果:
2016-04-0620:08:38.287MutiThread[4927:1577193]剩余票數:10窗口:北京售票窗口 2016-04-0620:08:38.489MutiThread[4927:1577194]剩余票數:9窗口:廣州售票窗口 2016-04-0620:08:38.690MutiThread[4927:1577193]剩余票數:8窗口:北京售票窗口 2016-04-0620:08:38.892MutiThread[4927:1577194]剩余票數:7窗口:廣州售票窗口 2016-04-0620:08:39.094MutiThread[4927:1577193]剩余票數:6窗口:北京售票窗口 2016-04-0620:08:39.294MutiThread[4927:1577194]剩余票數:5窗口:廣州售票窗口 2016-04-0620:08:39.499MutiThread[4927:1577193]剩余票數:4窗口:北京售票窗口 2016-04-0620:08:39.700MutiThread[4927:1577194]剩余票數:3窗口:廣州售票窗口 2016-04-0620:08:39.905MutiThread[4927:1577193]剩余票數:2窗口:北京售票窗口 2016-04-0620:08:40.106MutiThread[4927:1577194]剩余票數:1窗口:廣州售票窗口 2016-04-0620:08:40.312MutiThread[4927:1577193]剩余票數:0窗口:北京售票窗口 2016-04-0620:08:40.516MutiThread[4927:1577194]售賣完畢 2016-04-0620:08:40.516MutiThread[4927:1577193]售賣完畢 2016-04-0620:08:40.517MutiThread[4927:1577193]
{number=2,name=北京售票窗口}WillExit 2016-04-0620:08:40.517MutiThread[4927:1577194]
{number=3,name=廣州售票窗口}
WillExit
如果確定兩個線程都是isCancelled狀態,可以調用[NSThread exit]方法來終止線程。
//創建線程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector() object:nil];
//object存的是參數
//修改參數名
thread.name = @"我的多線程";
//獲取主線程
NSThread *thread = [NSThread mainThread];
//創建線程並自動啟動
[NSThread detachNewThreadSelector:@selector() toTarget:self withObject:nil];
//隱士創建
[self performSelectorInBackground:@selector() withObject:nil];
//獲取當前線程
[NSThread currentThread]
//啟動線程
- (void)start;
//阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態
//停止當前正在運行的進程
+ (void)exit;
// 進入死亡狀態,一旦死亡則不能重啟
互斥鎖使用格式
@synchronized(鎖對象) { // 需要鎖定的代碼 }只用一把鎖,多鎖是無效的互斥鎖的優缺點
優點:能有效防止因多線程搶奪資源造成的數據安全問題缺點:需要消耗大量的CPU資源互斥鎖的使用前提:多條線程搶奪同一塊資源
互斥鎖的示例代碼
#import "ViewController.h"
@interface ViewController ()
/** 售票機1*/
@property (strong,nonatomic) NSThread *threadOne;
/** 售票機2*/
@property (strong,nonatomic) NSThread *threadTwo;
/** 售票機3*/
@property (strong,nonatomic) NSThread *threadThree;
/** 售票機4*/
@property (strong,nonatomic) NSThread *threadFour;
/** 售票機5*/
@property (strong,nonatomic) NSThread *threadFive;
/** 數量*/
@property (assign,nonatomic) NSInteger count;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//創建線程
self.threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"one"];
self.threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"two"];
self.threadThree = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"three"];
self.threadFour = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"four"];
self.threadFive = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"five"];
self.count = 100;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//開始線程
[self.threadOne start];
[self.threadTwo start];
[self.threadThree start];
[self.threadFour start];
[self.threadFive start];
}
- (void)run:(NSString *)param
{
while (1) {
//self是鎖對象
@synchronized(self)
{
if (self.count > 0) {
_count--;
NSLog(@"%zd-----%@",self.count,[NSThread currentThread]);
}
else
{
NSLog(@"賣完了----%@",[NSThread currentThread]);
break;
}
}
}
}
@end