iOS NSThread進階 詳

當(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,262評論 8 265
  • iOS多線程實踐中,常用的就是子線程執(zhí)行耗時操作,然后回到主線程刷新UI。在iOS中每個進程啟動后都會建立一個主線...
    jackyshan閱讀 1,575評論 2 12
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項。當(dāng)然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 699評論 0 0
  • 一、多線程基礎(chǔ) 基本概念 進程進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序每個進程之間是獨立的,每個進程均運行在其專用且...
    AlanGe閱讀 651評論 0 0
  • 所有的不安與依賴,都是因為自己不夠強大
    荷塘悅色閱讀 189評論 0 0

友情鏈接更多精彩內(nèi)容