當(dāng)需要對一個知識詳細深入了解的時候,最怕的事情就是,官方文檔什么也不說就是光溜溜的代碼,就像NSThread,注釋官方文檔奉上。
@interface NSThread : NSObject {
@private
id _private;
uint8_t _bytes[44];
}
// 獲取當(dāng)前線程
+ (NSThread *)currentThread;
// 創(chuàng)建新線程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
// 是否是多線程
+ (BOOL)isMultiThreaded;
/**
* 每個線程都維護了一個“鍵-值”的字典,它可以在線程里面的任何地方被訪問,
* 可以使用該字典來保存一些信息,這些信息在整個線程的執(zhí)行過程中都保持不變。
* 比如,可以使用它來存儲在整個線程過程中RunLoop里面多次迭代的狀態(tài)信息。
* 使用:通過threadDictionary方法獲取一個NSMutableDictionary對象,然后添加需要的字段和數(shù)據(jù)
*/
@property (readonly, retain) NSMutableDictionary *threadDictionary;
// 設(shè)置線程睡眠/堵塞
+ (void)sleepUntilDate:(NSDate *)date;
// 設(shè)置線程睡眠/堵塞
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 結(jié)束/退出進程
+ (void)exit;
// 獲取線程的優(yōu)先級
+ (double)threadPriority;
// 設(shè)置線程優(yōu)先級,取值范圍0.0~1.0
+ (BOOL)setThreadPriority:(double)p;
// 線程優(yōu)先級,iOS8以后推薦使用qualityOfService屬性,通過量化的優(yōu)先級枚舉值來設(shè)置
@property double threadPriority;
/** 線程優(yōu)先級
qualityOfService的枚舉值如下:
NSQualityOfServiceUserInteractive:最高優(yōu)先級,用于用戶交互事件
NSQualityOfServiceUserInitiated:次高優(yōu)先級,用于用戶需要馬上執(zhí)行的事件
NSQualityOfServiceDefault:默認優(yōu)先級,主線程和沒有設(shè)置優(yōu)先級的線程都默認為這個優(yōu)先級
NSQualityOfServiceUtility:普通優(yōu)先級,用于普通任務(wù)
NSQualityOfServiceBackground:最低優(yōu)先級,用于不重要的任務(wù)
*/
@property NSQualityOfService qualityOfService;
// 返回當(dāng)前線程在棧中所占的地址所組成的數(shù)組
+ (NSArray<NSNumber *> *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
// 返回??臻g的符號表
+ (NSArray<NSString *> *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
// 線程名稱
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
// 棧的所占空間大小
@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);
// 是否是主線程
@property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
// 判斷當(dāng)前線程是否是主線程
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
// 獲取主線程
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
// 初始化線程
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
// 初始化線程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
// 是否正在執(zhí)行
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
// 是否執(zhí)行完畢
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
// 是否已經(jīng)取消/中止
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
// 取消線程,不能再開始
- (void)cancel NS_AVAILABLE(10_5, 2_0);
// 開始線程
- (void)start NS_AVAILABLE(10_5, 2_0);
/** main是線程入口
* - (void)main的使用:
* 1. 一般創(chuàng)建線程會子類化NSThread,重寫main方法,把關(guān)于線程執(zhí)行的方法都寫在里面,這樣可以在任何需要這個線程方法的地方直接使用。
* 2. 把線程執(zhí)行的方法寫在main里,是因為線程的操作應(yīng)該屬于線程的本身,而不是每次使用都通過initWithTarget:selector:object:方法,且再一次實現(xiàn)某個方法。
* 3. 當(dāng)重寫了main方法后,同時使用initWithTarget:selector:object:方法初始化,調(diào)用某個方法執(zhí)行任務(wù),系統(tǒng)默認只執(zhí)行main方法里面的任務(wù)。
* 4. 如果直接使用NSThread創(chuàng)建線程,線程內(nèi)執(zhí)行的方法都是在當(dāng)前的類文件里面的。
*/
- (void)main NS_AVAILABLE(10_5, 2_0);
@end
FOUNDATION_EXPORT NSNotificationName const NSWillBecomeMultiThreadedNotification;
FOUNDATION_EXPORT NSNotificationName const NSDidBecomeSingleThreadedNotification;
FOUNDATION_EXPORT NSNotificationName const NSThreadWillExitNotification;
@interface NSObject (NSThreadPerformAdditions)
/**
將需要執(zhí)行的任務(wù)放到主線程進行操作
* aSelector:方法Id
* arg:需要傳的參數(shù)
* wait:指定,當(dāng)前線程是否要被阻塞,直到主線程將我們制定的代碼塊(RefreshCellForLiveId:方法)執(zhí)行完。
注意:
1.當(dāng)前線程為主線程的時候,waitUntilDone:YES參數(shù)無效。
2.該方法,沒有返回值
3.該方法主要用來用主線程來修改頁面UI的狀態(tài)。
4.modes:(nullable NSArray<NSString *> *)array 這個參數(shù)我不知道從何而來
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
//指定任務(wù)在特定的線程上執(zhí)行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
//指定任務(wù)到后臺執(zhí)行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
NSThread線程創(chuàng)建
方式一:
- (void)viewDidLoad {
[super viewDidLoad];
//此方式需要調(diào)用start方法
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(text_thread:) object:@"init"];
[thread start];
}
- (void)text_thread:(id)obj{
NSLog(@"obj:%@\ncurrentThread:%@",obj,[NSThread currentThread]);
}
打印數(shù)據(jù)
2018-04-26 15:33:28.321263+0700 GCD[79984:4079183] obj: init
currentThread:<NSThread: 0x60000046a000>{number = 3, name = (null)}
方式二
- (void)viewDidLoad {
[super viewDidLoad];
//此方式不需要調(diào)用start方法
[NSThread detachNewThreadSelector:@selector(text_thread:) toTarget:self withObject:@"detachNew"];
}
- (void)text_thread:(id)obj{
NSLog(@"obj:%@\ncurrentThread:%@",obj,[NSThread currentThread]);
}
打印數(shù)據(jù)
2018-04-26 15:41:02.738780+0700 GCD[80110:4086842] obj:detachNew
currentThread:<NSThread: 0x60000027b580>{number = 3, name = (null)}
方式三
- (void)viewDidLoad {
[super viewDidLoad];
//perform 創(chuàng)建三種方式
// 這三個方法都是同步執(zhí)行,與線程無關(guān),在需要動態(tài)的去調(diào)用方法的時候去使用
//[self performSelector:@selector(threadRun)];
//[self performSelector:@selector(threadRun) withObject:nil];
//[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];
//此方式不需要調(diào)用start方法
[self performSelectorInBackground:@selector(text_thread:)withObject:@"perform"];
}
- (void)text_thread:(id)obj{
NSLog(@"obj:%@\ncurrentThread:%@",obj,[NSThread currentThread]);
}
打印數(shù)據(jù):
2018-04-26 15:46:45.520661+0700 GCD[80178:4091323] obj:perform
currentThread:<NSThread: 0x604000263c40>{number = 3, name = (null)}
NSThread屬性設(shè)置
- (void)viewDidLoad {
[super viewDidLoad];
//此方式不需要調(diào)用start方法
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(text_thread:) object:@"線程屬性"];
//設(shè)置線程名稱
thread2.name = @"download B";
/**threadPriority:優(yōu)先級的取值范圍為0.0-1.0,線程默認優(yōu)先級是0.5,最高是1.0。
優(yōu)先級高只能說明 CPU 在調(diào)度的時候,會優(yōu)先調(diào)度,并不意味著優(yōu)先級低的就不被調(diào)用或者后調(diào)用!
在多線程開發(fā)的時候,不要去做不同線程之間執(zhí)行的比較!線程內(nèi)部的方法都是各自獨立執(zhí)行的,如果設(shè)置了優(yōu)先級,那么就會有可能出現(xiàn)低優(yōu)先級的線程阻塞高優(yōu)先級的線程,也就是優(yōu)先級反轉(zhuǎn)!在ios開發(fā)中,多線程最主要的目的就是把耗時操作放在后臺執(zhí)行,一半不做修改**/
thread2.threadPriority = 0;
//修改當(dāng)前線程棧區(qū)大小 單位KB 默認512KB 一般不做修改
thread2.stackSize = 11024 * 1024;
[thread2 start];
}
- (void)text_thread:(id)obj{
NSLog(@"obj:%@\ncurrentThread:%@\n主線程棧區(qū)空間大小:%ld",obj,[NSThread currentThread],[NSThread currentThread].stackSize);
}
打印數(shù)據(jù):
2018-04-26 16:08:29.396115+0700 GCD[80413:4108213] obj:線程屬性
currentThread:<NSThread: 0x6000004701c0>{number = 3, name = download B}
主線程棧區(qū)空間大小:11288576
阻塞線程
// 設(shè)置線程睡眠/堵塞
+ (void)sleepUntilDate:(NSDate *)date;
// 設(shè)置線程睡眠/堵塞
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
注意:這兩個方法相同
[NSThread sleepForTimeInterval:5.0]也就等于[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]
取消,判斷取消 以及退出方法
//開始
- (IBAction)start:(id)sender {
_thread = [[NSThread alloc]initWithTarget:self selector:@selector(startCount) object:nil];
[_thread start];
}
//取消線程
- (IBAction)cancel:(id)sender {
//并沒有真正取消該線程,只是給該線程設(shè)置了一個標(biāo)志位
NSLog(@"取消線程");
[_thread cancel];
NSLog(@"_thread:%@",_thread);
}
//執(zhí)行任務(wù)
- (void)startCount{
for (NSInteger i = 0; i< 1000; i++) {
//根據(jù)線程是否取消的標(biāo)志位退出該任務(wù) 退出該任務(wù)后會發(fā)現(xiàn)全部不執(zhí)行了_thread 也沒有進行打印
if (_thread.cancelled) {
[NSThread exit];
NSLog(@"退出:_thread:%@",_thread);
return;
}
[NSThread sleepForTimeInterval:1.0f];
NSLog(@"%ld",i);
}
}
打印數(shù)據(jù):
2018-04-26 20:47:32.739797+0700 GCDCC[41269:2619697] 0
2018-04-26 20:47:33.743628+0700 GCDCC[41269:2619697] 1
2018-04-26 20:47:34.747350+0700 GCDCC[41269:2619697] 2
2018-04-26 20:47:35.751858+0700 GCDCC[41269:2619697] 3
2018-04-26 20:47:36.394613+0700 GCDCC[41269:2617587] 取消線程
2018-04-26 20:47:36.395097+0700 GCDCC[41269:2617587] _thread:<NSThread: 0x604000465100>{number = 8, name = main}
2018-04-26 20:47:36.754946+0700 GCDCC[41269:2619697] 4
主線程相關(guān)方法
//此處不做解釋
+ (NSThread*)mainThread;// 獲得主線程
- (BOOL)isMainThread;// 是否為主線程
+ (BOOL)isMainThread;// 是否為主線程
指定任務(wù)線程到主線程進行操作
說明:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/**
將需要執(zhí)行的任務(wù)放到主線程進行操作
- aSelector:方法Id
- arg:需要傳的參數(shù)
- wait:指定,當(dāng)前線程是否要被阻塞,直到主線程將我們制定的代碼塊(RefreshCellForLiveId:方法)執(zhí)行完
- array:暫時未找到用法
注意:
1.當(dāng)前線程為主線程的時候,waitUntilDone:YES參數(shù)無效。
2.該方法,沒有返回值
3.該方法主要用來用主線程來修改頁面UI的狀態(tài)。
*/
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"currentThread:%@",[NSThread currentThread]);
[self performSelectorOnMainThread:@selector(text_thread:) withObject:@"指定到主線程操作" waitUntilDone:YES];
});
}
- (void)text_thread:(id)obj{
NSLog(@"obj:%@\ncurrentThread:%@",obj,[NSThread currentThread]);
}
打印數(shù)據(jù):
2018-04-26 17:08:12.827038+0700 GCD[81044:4152136] currentThread:<NSThread: 0x60000026b040>{number = 3, name = (null)}
2018-04-26 17:08:12.830341+0700 GCD[81044:4152040] obj:指定到主線程操作
currentThread:<NSThread: 0x60000006c4c0>{number = 1, name = main}
指定任務(wù)線程到指定的線程上執(zhí)行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait
指定任務(wù)到后臺執(zhí)行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
線程枷鎖
線程同步 線程和其他線程可能會共享一些資源,當(dāng)多個線程同時讀寫同一份共享資源的時候,可能會引起沖突。線程同步是指是指在一定的時間內(nèi)只允許某一個線程訪問某個資源
iOS實現(xiàn)線程加鎖有NSLock和@synchronized, dispatch_semaphore 三種方式
銷售火車票
- (void)viewDidLoad {
[super viewDidLoad];
//先監(jiān)聽線程退出的通知,以便知道線程什么時候退出
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil];
_ticketCount = 10;
//新建兩個子線程(代表兩個窗口同時銷售門票)
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];
}
- (void)threadExitNotice{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",[NSThread currentThread]);
[NSThread exit];
}
//線程啟動后,執(zhí)行saleTicket,執(zhí)行完畢后就會退出,為了模擬持續(xù)售票的過程,我們需要給它加一個循環(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;
}
}
}
打印數(shù)據(jù):
2018-04-26 21:55:03.736479+0700 GCDCC[41894:2658144] 剩余票數(shù):8 窗口:廣州售票窗口
2018-04-26 21:55:03.736480+0700 GCDCC[41894:2658143] 剩余票數(shù):9 窗口:北京售票窗口
2018-04-26 21:55:03.939814+0700 GCDCC[41894:2658143] 剩余票數(shù):7 窗口:北京售票窗口
2018-04-26 21:55:03.946772+0700 GCDCC[41894:2658144] 剩余票數(shù):6 窗口:廣州售票窗口
2018-04-26 21:55:04.151320+0700 GCDCC[41894:2658143] 剩余票數(shù):5 窗口:北京售票窗口
2018-04-26 21:55:04.156364+0700 GCDCC[41894:2658144] 剩余票數(shù):4 窗口:廣州售票窗口
2018-04-26 21:55:04.352419+0700 GCDCC[41894:2658143] 剩余票數(shù):3 窗口:北京售票窗口
2018-04-26 21:55:04.359034+0700 GCDCC[41894:2658144] 剩余票數(shù):2 窗口:廣州售票窗口
2018-04-26 21:55:04.554506+0700 GCDCC[41894:2658143] 剩余票數(shù):1 窗口:北京售票窗口
2018-04-26 21:55:04.563299+0700 GCDCC[41894:2658144] 剩余票數(shù):0 窗口:廣州售票窗口
2018-04-26 22:06:17.331139+0700 GCDCC[42004:2665469] <NSThread: 0x604000272280>{number = 3, name = 北京售票窗口}
2018-04-26 22:06:17.535072+0700 GCDCC[42004:2665470] <NSThread: 0x604000272000>{number = 4, name = 廣州售票窗口}
總結(jié):可以看到,票的銷售過程中出現(xiàn)了剩余數(shù)量錯亂的情況,這就是前面提到的線程同步問題。
售票是一個典型的需要線程同步的場景,由于售票渠道有很多,而票的資源是有限的,當(dāng)多個渠道在短時間內(nèi)賣出大量的票的時候,如果沒有同步機制來管理票的數(shù)量,將會導(dǎo)致票的總數(shù)和售出票數(shù)對應(yīng)不上的錯誤。
優(yōu)化一
- (void)viewDidLoad {
[super viewDidLoad];
//先監(jiān)聽線程退出的通知,以便知道線程什么時候退出
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil];
_ticketCount = 10;
//新建兩個子線程(代表兩個窗口同時銷售門票)
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];
[self performSelector:@selector(saleTicket) onThread:window1 withObject:nil waitUntilDone:NO];
[self performSelector:@selector(saleTicket) onThread:window2 withObject:nil waitUntilDone:NO];
}
- (void)threadExitNotice{
NSLog(@"%@",[NSThread currentThread]);
[NSThread exit];
}
//接著我們給線程創(chuàng)建一個runLoop
- (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]]; //自定義運行時間
}
- (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());
}
}
}
}
}
打印數(shù)據(jù):
2018-04-26 22:19:41.665526+0700 GCDCC[42140:2674733] 剩余票數(shù):9 窗口:北京售票窗口
2018-04-26 22:19:41.866523+0700 GCDCC[42140:2674734] 剩余票數(shù):8 窗口:廣州售票窗口
2018-04-26 22:19:42.067599+0700 GCDCC[42140:2674733] 剩余票數(shù):7 窗口:北京售票窗口
2018-04-26 22:19:42.270942+0700 GCDCC[42140:2674734] 剩余票數(shù):6 窗口:廣州售票窗口
2018-04-26 22:19:42.473745+0700 GCDCC[42140:2674733] 剩余票數(shù):5 窗口:北京售票窗口
2018-04-26 22:19:42.674902+0700 GCDCC[42140:2674734] 剩余票數(shù):4 窗口:廣州售票窗口
2018-04-26 22:19:42.878208+0700 GCDCC[42140:2674733] 剩余票數(shù):3 窗口:北京售票窗口
2018-04-26 22:19:43.080097+0700 GCDCC[42140:2674734] 剩余票數(shù):2 窗口:廣州售票窗口
2018-04-26 22:19:43.280505+0700 GCDCC[42140:2674733] 剩余票數(shù):1 窗口:北京售票窗口
2018-04-26 22:19:43.483755+0700 GCDCC[42140:2674734] 剩余票數(shù):0 窗口:廣州售票窗口
2018-04-26 22:19:43.687034+0700 GCDCC[42140:2674733] 售賣完畢
2018-04-26 22:19:43.687204+0700 GCDCC[42140:2674734] 售賣完畢
2018-04-26 22:19:43.687649+0700 GCDCC[42140:2674733] <NSThread: 0x604000275340>{number = 3, name = 北京售票窗口}
2018-04-26 22:19:43.687915+0700 GCDCC[42140:2674734] <NSThread: 0x604000275580>{number = 4, name = 廣州售票窗口}
如果確定兩個線程都是isCancelled狀態(tài),可以調(diào)用[NSThread exit]方法來終止線程。
優(yōu)化二
@interface ViewController ()
{
NSInteger _ticketCount;
NSLock *lock;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
//先監(jiān)聽線程退出的通知,以便知道線程什么時候退出
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil];
_ticketCount = 10;
lock = [[NSLock alloc]init];
//新建兩個子線程(代表兩個窗口同時銷售門票)
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];
[self performSelector:@selector(saleTicket) onThread:window1 withObject:nil waitUntilDone:NO];
[self performSelector:@selector(saleTicket) onThread:window2 withObject:nil waitUntilDone:NO];
}
- (void)threadExitNotice{
NSLog(@"%@",[NSThread currentThread]);
[NSThread exit];
}
//接著我們給線程創(chuàng)建一個runLoop
- (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]]; //自定義運行時間
}
- (void)saleTicket {
while (1) {
[lock lock];
//如果還有票,繼續(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());
}
}
//解鎖
[lock unlock];
}
}
優(yōu)化三 請參考我的文章
GCDiOS GCD詳 線程加鎖方式
http://www.itdecent.cn/p/97ed78a6f9b8