上篇文章講了一下NSThread的基本操做,本篇講一下NSThread的一些高級用法。程序員
線程間資源共享&線程加鎖安全
在程序運行過程當中,若是存在多線程,呢麼各個線程讀寫資源就會存在前後、同時讀寫資源的操做,由於實在不一樣線程中,CPU調度過程當中咱們沒法保證哪一個線程會先讀寫資源,哪一個線程後讀寫資源。這就有可能操做數據混亂和錯誤。所以爲了防止數據讀寫混亂和錯誤的發生,咱們要將線程在讀寫數據時加鎖,這樣就能保證操做同一個數據UI小的線程只有一個,當這個線程執行完成以後解鎖,其餘的線程才能操做此數據對象。NSLock/NSConditiongLock/NSRecursivelovk/@synchronized均可以實現線程上鎖的操做。網絡
1.@synchronized多線程
直接上例子:12306搶火車票函數
// 首先:開啓兩個線程同時售票 self.tickets = 20; NSThread *t1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil]; t1.name = @"售票員A"; [t1 start]; NSThread *t2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTickets) object:nil]; t2.name = @"售票員B"; [t2 start]; // 而後:將售票的方法加鎖 - (void)saleTickets{ while (YES) { [NSThread sleepForTimeInterval:1.0]; //互斥鎖 -- 保證鎖內的代碼在同一時間內只有一個線程在執行 @synchronized (self){ //1.判斷是否有票 if (self.tickets > 0) { //2.若是有就賣一張 self.tickets --; NSLog(@"還剩%d張票 %@",self.tickets,[NSThread currentThread]); }else{ //3.沒有票了提示 NSLog(@"賣完了 %@",[NSThread currentThread]); break; } } } }
2.NSLockoop
-(BOOL)tryLock;//嘗試加鎖,成功返回YES ;失敗返回NO ,但不會阻塞線程的運行 -(BOOL)lockBeforeDate:(NSDate *)limit;//在指定的時間之前獲得鎖。YES:在指定時間以前得到了鎖;NO:在指定時間以前沒有得到鎖。該線程將被阻塞,直到得到了鎖,或者指定時間過時。 - (void)setName:(NSString*)newName//爲鎖指定一個Name - (NSString*)name//**返回鎖指定的**name @property (nullable, copy) NSString *name;線程鎖名稱
舉個例子性能
NSLock *myLock = [[NSLock alloc] init]; static NSString *str = @"hello"; [NSThread detachNewThreadWithBlock:^{ NSLog(@"%d",[myLock tryLock]); [myLock lock]; NSLog(@"%d",[myLock tryLock]); NSLog(@"%@",str); str = @"123"; [myLock unlock]; }]; [NSThread detachNewThreadWithBlock:^{ [myLock lock]; NSLog(@"%@",str); str = @"11111"; [myLock unlock]; }];
輸出結果不加鎖以前,兩個線程輸出同樣都是hello;枷鎖以後,輸出就會改變hello和123.測試
3.NSConditionLockatom
使用此鎖,在線程沒有得到鎖的狀況下,阻塞,即暫停運行,典型用於生產者/消費者模型。spa
- (instancetype)initWithCondition:(NSInteger)condition;//初始化條件鎖 - (void)lockWhenCondition:(NSInteger)condition;//加鎖 (條件是:鎖空閒,即沒被佔用;條件成立) - (BOOL)tryLock; //嘗試加鎖,成功返回TRUE,失敗返回FALSE - (BOOL)tryLockWhenCondition:(NSInteger)condition;//在指定條件成立的狀況下嘗試加鎖,成功返回TRUE,失敗返回FALSE - (void)unlockWithCondition:(NSInteger)condition;//在指定的條件成立時,解鎖 - (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前加鎖,成功返回TRUE,失敗返回FALSE, - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//條件成立的狀況下,在指定時間前加鎖,成功返回TRUE,失敗返回FALSE, @property (readonly) NSInteger condition;//條件鎖的條件 @property (nullable, copy) NSString *name;//條件鎖的名稱
舉個例子:
NSConditionLock *conditionLock = [[NSConditionLock alloc] init]; [NSThread detachNewThreadWithBlock:^{ for (int i = 0; i < 5; i++) { [conditionLock lock]; NSLog(@"當前解鎖條件:%d",i); sleep(2); [conditionLock unlockWithCondition:i]; BOOL isLocked = [conditionLock tryLockWhenCondition:2]; if (isLocked) { NSLog(@"%d加鎖成功!!!!!",i); [conditionLock unlock]; } else { NSLog(@"%d加鎖失敗!!!!!",i); } } }];
輸出結果:
4.NSRecursiveLock
此鎖可再同一線程中屢次被使用,但要保證加鎖和解鎖使用平衡,多用於遞歸函數,防止死鎖。
- (BOOL)tryLock;//嘗試加鎖,成功返回TRUE,失敗返回FALSE - (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前嘗試加鎖,成功返回TRUE,失敗返回FALSE @property (nullable, copy) NSString *name;//線程鎖名稱
舉個例子:
-(void)initRecycle:(int)value { [self.myRecursive lock]; if(value>0) { NSLog(@"當前的value值:%d",value); sleep(2); [self initRecycle:value-1]; } [self.myRecursive unlock]; }
線性安全之原子屬性atomic
原子屬性(線程安全)與非原子屬性,是什麼意思呢?
蘋果系統在咱們聲明對象屬性時默認是atomic,也就是在讀寫這個屬性時,保證同一時間內只有一個線程能都執行,當聲明時用的是atomic,一般會生成_成員變量,若是同時重寫了getter&setter方法,_a成員變量就不自動生成。實際上原子屬性內部有一個鎖,叫作自旋鎖。
自旋鎖和互斥鎖
共同點 都可以保證線程安全 不一樣點 互斥鎖:若是其餘線程正在執行鎖定的代碼,此線程就會進入休眠狀態,等待鎖打開;而後被喚醒 自旋鎖:若是線程被鎖在外面,那麼就會用死循環的方式一直等待鎖打開! 自旋鎖是一種互斥鎖的實現方式,相比較通常的互斥鎖會在等待期間放棄cpu,自旋鎖則是不斷循環並測試鎖的狀態,會一直佔用cpu。 互斥鎖:用於保護臨界區,確保同一時間只有一個線程訪問數據。對共享資源的訪問,先對互斥量進行加鎖,若是互斥量已經上鎖,調用線程會阻塞,直到互斥量被解鎖。在完成了對共享資源的訪問後,要對互斥量進行解鎖。 臨界區:每一個進程中訪問臨界資源的那段程序稱爲臨界區,每次只容許一個進程進入臨界區,進入後不容許其餘進程進入。 自旋鎖:與互斥量相似,它不是經過休眠使進程阻塞,而是在獲取鎖以前一直處於忙等(自旋)阻塞狀態。用在如下狀況:鎖持有的時間短,並且線程並不但願在從新調度上花太多的成本。"原地打轉"。 信號量:信號量是一個計數器,能夠用來控制多個進程對共享資源的訪問。它常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。
不管什麼鎖,都很消耗性能,效率不高,因此在咱們平時開發過程當中,會使用nonatomic。
舉個例子
@property (strong, nonatomic) NSObject *myNonatomic; @property (strong, atomic) NSObject *myAtomic; // 當咱們重寫了myAtomic的setter和getter方法時 - (void)setMyAtomic:(NSObject *)myAtomic{ _myAtomic = myAtomic; } - (NSObject *)myAtomic{ return _myAtomic; } // 那麼咱們就必須聲明一個_myAtomic靜態變量 @synthesize myAtomic = _myAtomic; // 不然系統在編譯的時候找不到 _myAtomic
子線程的Runloop
在介紹子線程上的Runloop以前先來一個有意思的小插曲,咱們來介紹一下Runloop,甚至模擬一個Runloop
Runloop 運行循環
-在目前iOS開發中,幾乎用不到,在之前iOS黑暗時代,程序員會用到
目的:
保證程序不退出
監聽事件
沒有事件讓程序進入休眠
區分模式:
NSDefaultRunLoopMode - 時鐘、網絡事件
NSRunLoopCommonModes - 用戶交互
// 模擬runloop void click(int type){ printf("正在運行第%d",type); } int main(int argc, const char * argv[]) { @autoreleasepool { while (YES) { printf("請輸入選項 0 表示退出"); int result = -1; scanf("%d",&result); if (result == 0) { printf("程序結束\n"); break; }else{ click(result); } } } return 0; }
@property (assign, nonatomic, getter=isFinished) BOOL finished; // 建立子線程並添加任務 NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil]; [t start]; self.finished = NO; [self performSelector:@selector(otherMethod) onThread:t withObject:nil waitUntilDone:NO]; // 在第一個任務中加入死循環 - (void)demo{ NSLog(@"%@",[NSThread currentThread]); //在OC中使用比較多的,退出循環的方式 while (!self.isFinished) { [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]]; } NSLog(@"能來嗎?"); } // 在最後添加的任務結束後結束死循環 - (void)otherMethod{ for (int i = 0; i < 10; i ++) { NSLog(@"%s %@",__FUNCTION__,[NSThread currentThread]); } //讓上面方法中的死循環結束 self.finished = YES; }