標籤:blog .com images bsp 異常處理 解決問題 test property max
多線程的安全隱患
一塊資源可能會被多個線程共用,也就是說多個線程可能會訪問同一塊資源。
比如多個線程同時操作同一個對象,同一個變數。
當多個線程訪問同一塊資源時,很容易引發資料錯亂和資料安全問題。
比如一個買票問題:
#import "ViewController.h"@interface ViewController ()@property (assign, nonatomic) NSInteger maxCount;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad];
_maxCount = 20; // 線程1 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 線程2 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; }- (void)run{ while (1) { if (_maxCount > 0) { // 暫停一段時間 [NSThread sleepForTimeInterval:0.002]; _maxCount--; NSLog(@"賣了一張票 - %ld - %@",_maxCount,[NSThread currentThread]); }else{ NSLog(@"票賣完了"); break; } }}
輸出結果:
可以看到,當多個線程同時訪問同一個資料的時候,很容易出現資料錯亂,資源爭奪的現象。
1. @synchronized(鎖對象) { // 需要鎖定的代碼 } 來解決
互斥鎖,使用的是線程同步的技術。加鎖的代碼需要盡量少,這個鎖 ?? 對象需要保持在多個線程中都是同一個對象。
優點:是不需要顯示的建立鎖對象,就可以實現鎖的機制。
缺點:會隱式的添加一個異常處理常式來保護代碼,該處理常式會在異常拋出的時候自動的釋放互斥鎖,會消耗系統資源。
- (void)run{ while (1) { // 這裡使用self,或者一個全域對象也行,每個對象裡面都有一把鎖 @synchronized(self){ if (_maxCount > 0) { // 暫停一段時間 [NSThread sleepForTimeInterval:0.002]; _maxCount--; NSLog(@"賣了一張票 - %ld - %@",_maxCount,[NSThread currentThread]); }else{ NSLog(@"票賣完了"); break; } } }}
輸出結果:
2. NSLock
在Cocoa架構中,NSLock實現了一個簡單的互斥鎖,所有鎖(包括NSLock)的介面,實際上都是通過NSLocking協議定義的,它定義了 lock(加鎖)和 unlock(解鎖)方法。
不能多次調用lock方法,會造成死結。
我們使用NSLock來解決上面的問題:
#import "ViewController.h"@interface ViewController ()@property (assign, nonatomic) NSInteger maxCount;@property (strong, nonatomic) NSLock *lock; // 資料所@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. _maxCount = 20; _lock = [[NSLock alloc] init]; // 線程1 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // 線程2 [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; }- (void)run{ while (1) { // 加鎖 [_lock lock]; if (_maxCount > 0) { // 暫停一段時間 [NSThread sleepForTimeInterval:0.002]; _maxCount--; NSLog(@"賣了一張票 - %ld - %@",_maxCount,[NSThread currentThread]); }else{ NSLog(@"票賣完了"); break; } // 釋放鎖 [_lock unlock]; }}
輸出結果:
3. NSRecursiveLock 遞迴鎖
如果在迴圈中,使用鎖,很容易造成死結。如下代碼,在遞迴block中,多次的調用lock方法,鎖會被多次的lock,所以自己也被阻塞了。
_lock = [[NSLock alloc] init]; //線程1 dispatch_async(dispatch_queue_create(NULL, 0), ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_lock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; NSLog(@"執行一次哦"); TestMethod(value--); } NSLog(@"是否執行到這裡"); [_lock unlock]; }; TestMethod(5); });
輸出內容:
2017-12-12 11:39:50.253155+0800 NSLock[1353:158620] 執行一次哦
此處將NSLock 換成 NSRecursiveLock 便可解決問題:
NSRecursiveLock類定義的鎖可以在同一線程多次lock,而不會造成死結。遞迴鎖會跟蹤它會被多少次lock,每次成功的lock都必須平衡調用unlock操作。只有所有的鎖住和解鎖操作都平衡的時候,鎖才真正被釋放給其它線程獲得。
_lock = [[NSRecursiveLock alloc] init]; //線程1 dispatch_async(dispatch_queue_create(NULL, 0), ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_lock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; NSLog(@"執行了一次哦"); TestMethod(value--); } NSLog(@"是否執行到這裡"); [_lock unlock]; }; TestMethod(5); });
執行結果:
2017-12-12 11:49:43.378299+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:44.380543+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:45.382145+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:46.387148+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:47.388813+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:48.389408+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:49.392983+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:50.396521+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:51.399108+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:52.399976+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:53.404280+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:54.409044+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:55.412670+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:56.413754+0800 NSLock[1419:176157] 執行了一次哦2017-12-12 11:49:57.414257+0800 NSLock[1419:176157] 執行了一次哦
4. NSConditionLock 條件鎖
NSConditionLock相比NSLock多了一個condition參數,我們可以理解為一個條件標示。
@property (readonly)NSInteger condition; //這屬性非常重要,外部傳入的condition與之相同才會擷取到lock對象,反之阻塞當前線程,直到condition相同- (void)lockWhenCondition:(NSInteger)condition; //condition與內部相同才會擷取鎖對象並立即返回,否則阻塞線程直到condition相同- (BOOL)tryLock;//嘗試擷取鎖對象,擷取成功需要配對unlock- (BOOL)tryLockWhenCondition:(NSInteger)condition; //同上- (void)unlockWithCondition:(NSInteger)condition; //解鎖,並且設定lock.condition = condition
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0]; //線程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if([cLock tryLockWhenCondition:0]){ NSLog(@"線程1"); [cLock unlockWithCondition:1]; }else{ NSLog(@"失敗"); } }); //線程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lockWhenCondition:3]; NSLog(@"線程2"); [cLock unlockWithCondition:2]; }); //線程3 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lockWhenCondition:1]; NSLog(@"線程3"); [cLock unlockWithCondition:3]; });
輸出結果:
2017-12-12 13:32:24.060013+0800 條件鎖 - NSConditionLock[1783:250080] 線程12017-12-12 13:32:24.060461+0800 條件鎖 - NSConditionLock[1783:250078] 線程32017-12-12 13:32:24.060626+0800 條件鎖 - NSConditionLock[1783:250079] 線程2
- 我們在初始化這個lock的時候,給定了它的初始條件為0;
- 執行tryLockWhenCondition:時,我們傳入的條件標示也是0,所以線程1加鎖成功。
- 執行unLockWithCondition:時,這時候會把condition將0改為1
- 因為condition為1,所以先走線程3,然後線程3將condition修改為3,
- 最後走了線程2
可以看出,NSConditionLock還可以實現任務之間的依賴。
5. NSCondition
NSCondition實際上作為一個鎖和一個線程檢查器。鎖主要是為了檢測條件時保護資料來源,執行條件引發的任務;線程檢查器主要是根據條件決定是否繼續進行下去,即線程是否被阻塞。
NSCondition實現了NSLocking協議,當多個線程訪問同一段代碼,會以wait為分水嶺,一個線程等待另一個線程unlock後,才會走wait之後的代碼。
[condition lock];//一般用於多線程同時訪問、修改同一個資料來源,保證在同一時間內資料來源只被訪問、修改一次,其他線程的命令需要在lock 外等待,只到unlock ,才可訪問[condition unlock];//與lock 同時使用[condition wait];//讓當前線程處於等待狀態[condition signal];//CPU發訊號告訴線程不用在等待,可以繼續執行
我們可以來看一個生產者消費者的問題,
消費者取得鎖,取產品,如果沒有產品,則wait等待,這時候會釋放鎖,知道有線程去喚醒它去消費產品。
生產者製造產品,首先也要取得鎖,然後生產,再發signal,這樣可以喚醒wait的消費者
#import "ViewController.h"@interface ViewController ()@property (strong, nonatomic) NSCondition *condition;@property (assign ,nonatomic) NSInteger goodNum;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; _goodNum = 0; _condition = [[NSCondition alloc] init]; [NSThread detachNewThreadSelector:@selector(buyGoods) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(shopGoods) toTarget:self withObject:nil]; }- (void)buyGoods{ [_condition lock]; while (_goodNum == 0){ NSLog(@"當前售賣個數為0,不能賣,等待"); [_condition wait]; } _goodNum--; NSLog(@"買了一個 - %ld",_goodNum); [_condition unlock];}- (void)shopGoods{ [_condition lock]; _goodNum++; NSLog(@"準備賣一個 - %ld",_goodNum); [_condition signal]; NSLog(@"告訴買家可以買了"); [_condition unlock];}
輸出結果:
2017-12-12 14:43:48.995787+0800 NSCondition[2410:357340] 當前售賣個數為0,不能賣,等待2017-12-12 14:43:48.996067+0800 NSCondition[2410:357341] 準備賣一個 - 12017-12-12 14:43:48.996578+0800 NSCondition[2410:357341] 告訴買家可以買了2017-12-12 14:43:48.997806+0800 NSCondition[2410:357340] 買了一個 - 0
我們可以看出,當消費者想要買東西的時候,因為商品初始數量為0,所以只能等待生產者去生產商品,準備售賣,有商品之後,會signal,告訴消費者可以買了。
6. 使用C語言的pthread_mutex_t實現的鎖
#import "ViewController.h"#import <pthread.h>@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; __block pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); //線程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&mutex); NSLog(@"線程1開始執行"); for (NSInteger i = 1; i <= 10; i++) { sleep(1); NSLog(@"線程1 在執行 ---- %ld",i); } pthread_mutex_unlock(&mutex); }); //線程2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ pthread_mutex_lock(&mutex); NSLog(@"線程1執行完了,線程2開始執行"); pthread_mutex_unlock(&mutex); }); }
輸出結果:
2017-12-12 14:54:16.343540+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1開始執行2017-12-12 14:54:17.348019+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 12017-12-12 14:54:18.348439+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 22017-12-12 14:54:19.351159+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 32017-12-12 14:54:20.355283+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 42017-12-12 14:54:21.358290+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 52017-12-12 14:54:22.361572+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 62017-12-12 14:54:23.362013+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 72017-12-12 14:54:24.363130+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 82017-12-12 14:54:25.366042+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 92017-12-12 14:54:26.370250+0800 pthread_mutex - 互斥鎖[2518:371518] 線程1 在執行 ---- 102017-12-12 14:54:26.370496+0800 pthread_mutex - 互斥鎖[2518:371519] 線程1執行完了,線程2開始執行
iOS中常見的鎖