NSInteger total = 0; - (void)threadNotSafe { for (NSInteger index = 0; index < 3; index++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ total += 1; NSLog(@"total: %ld", total); total -= 1; NSLog(@"total: %ld", total); }); } } //第一次輸出: 2017-11-28 23:34:11.551570+0800 BasicDemo[75679:5312246] total: 1 2017-11-28 23:34:11.551619+0800 BasicDemo[75679:5312248] total: 3 2017-11-28 23:34:11.551618+0800 BasicDemo[75679:5312249] total: 2 2017-11-28 23:34:11.552120+0800 BasicDemo[75679:5312246] total: 2 2017-11-28 23:34:11.552143+0800 BasicDemo[75679:5312248] total: 1 2017-11-28 23:34:11.552171+0800 BasicDemo[75679:5312249] total: 0 //第二次輸出 2017-11-28 23:34:55.738947+0800 BasicDemo[75683:5313401] total: 1 2017-11-28 23:34:55.738979+0800 BasicDemo[75683:5313403] total: 2 2017-11-28 23:34:55.738985+0800 BasicDemo[75683:5313402] total: 3 2017-11-28 23:34:55.739565+0800 BasicDemo[75683:5313401] total: 2 2017-11-28 23:34:55.739570+0800 BasicDemo[75683:5313402] total: 1 2017-11-28 23:34:55.739577+0800 BasicDemo[75683:5313403] total: 0 NSInteger total = 0; NSLock *lock = [NSLock new]; - (void)threadSafe { for (NSInteger index = 0; index < 3; index++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [lock lock]; total += 1; NSLog(@"total: %ld", total); total -= 1; NSLog(@"total: %ld", total); [lock unlock]; }); } } //第一次輸出 2017-11-28 23:35:37.696614+0800 BasicDemo[75696:5314483] total: 1 2017-11-28 23:35:37.696928+0800 BasicDemo[75696:5314483] total: 0 2017-11-28 23:35:37.696971+0800 BasicDemo[75696:5314481] total: 1 2017-11-28 23:35:37.696995+0800 BasicDemo[75696:5314481] total: 0 2017-11-28 23:35:37.697026+0800 BasicDemo[75696:5314482] total: 1 2017-11-28 23:35:37.697050+0800 BasicDemo[75696:5314482] total: 0 //第二次輸出 2017-11-28 23:36:01.790264+0800 BasicDemo[75700:5315159] total: 1 2017-11-28 23:36:01.790617+0800 BasicDemo[75700:5315159] total: 0 2017-11-28 23:36:01.790668+0800 BasicDemo[75700:5315161] total: 1 2017-11-28 23:36:01.790687+0800 BasicDemo[75700:5315161] total: 0 2017-11-28 23:36:01.790711+0800 BasicDemo[75700:5315160] total: 1 2017-11-28 23:36:01.790735+0800 BasicDemo[75700:5315160] total: 0
從上面能夠看出,第一個函數第一次和第二次調用的結果不同,換句話說,不能肯定代碼的運行順序和結果,是線程不安全的;第二個函數第一次和第二次輸出結果同樣,能夠肯定函數的執行結果,是線程安全的。html
線程不安全是因爲多線程訪問形成的,那麼如何解決?算法
1.既然線程安全問題是由多線程引發的,那麼,最極端的可使用單線程保證線程安全。api
2.線程安全是因爲多線程訪問和修改共享資源而引發不可預測的結果,所以,若是都是訪問共享資源而不去修改共享資源也能夠保證線程安全,好比:設置只讀屬性的全局變量。數組
3.使用鎖。緩存
鎖是最經常使用的同步工具。一段代碼段在同一個時間只能容許被一個線程訪問,好比一個線程A進入加鎖代碼以後因爲已經加鎖,另外一個線程B就沒法訪問,只有等待前一個線程A執行完加鎖代碼後解鎖,B線程才能訪問加鎖代碼。不要將過多的其餘操做代碼放到裏面,不然一個線程執行的時候另外一個線程就一直在等待,就沒法發揮多線程的做用了。安全
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lock]; if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObject:imageName]; } [lock unlock]; }
每一個iOS開發最先接觸的線程鎖就是@synchronized,代碼簡單。數據結構
- (void)getIamgeName:(int)index{ NSString *imageName; @synchronized(self) { if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObject:imageName]; } } }
dispatch_semaphore_t
GCD中信號量,也能夠解決資源搶佔問題,支持信號通知和信號等待。每當發送一個信號通知,則信號量+1;每當發送一個等待信號時信號量-1,;若是信號量爲0則信號會處於等待狀態,直到信號量大於0開始執行。多線程
#import "DispatchSemaphoreViewController.h" @interface DispatchSemaphoreViewController () { dispatch_semaphore_t semaphore; } @end @implementation DispatchSemaphoreViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. semaphore = dispatch_semaphore_create(1); /** * 建立一個信號量爲1的信號 * */ } - (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; /** * semaphore:等待信號 DISPATCH_TIME_FOREVER:等待時間 wait以後信號量-1,爲0 */ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObject:imageName]; } /** * 發送一個信號通知,這時候信號量+1,爲1 */ dispatch_semaphore_signal(semaphore); } @end
NSCondition一樣實現了NSLocking協議,因此它和NSLock同樣,也有NSLocking協議的lock和unlock方法,能夠當作NSLock來使用解決線程同步問題,用法徹底同樣。併發
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lock]; if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObject:imageName]; } [lock unlock]; }
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lock]; //加鎖 static int m = 0; static int n = 0; static int p = 0; NSLog(@"removeObjectBegin count: %ld\n",imageNames.count); if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObjectAtIndex:0]; m++; NSLog(@"執行了%d次刪除操做",m); } else { p++; NSLog(@"執行了%d次等待",p); [lock wait]; //等待 imageName = [imageNames lastObject]; [imageNames removeObjectAtIndex:0]; /** * 有時候點擊取出圖片會崩潰 */ n++; NSLog(@"執行了%d次繼續操做",n); } NSLog(@"removeObject count: %ld\n",imageNames.count); [lock unlock]; //解鎖 } - (void)createImageName:(NSMutableArray *)imageNames{ [lock lock]; static int m = 0; [imageNames addObject:@"0"]; m++; NSLog(@"添加了%d次",m); [lock signal]; //喚醒隨機一個線程取消等待繼續執行 // [lock broadcast]; //喚醒全部線程取消等待繼續執行 NSLog(@"createImageName count: %ld\n",imageNames.count); [lock unlock]; } #pragma mark - 多線程取出圖片後刪除 - (void)getImageNameWithMultiThread{ [lock broadcast]; NSMutableArray *imageNames = [[NSMutableArray alloc]init]; dispatch_group_t dispatchGroup = dispatch_group_create(); __block double then, now; then = CFAbsoluteTimeGetCurrent(); for (int i=0; i<10; i++) { dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){ [self getIamgeName:imageNames]; }); dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){ [self createImageName:imageNames]; }); } dispatch_group_notify(dispatchGroup, self.synchronizationQueue, ^(){ now = CFAbsoluteTimeGetCurrent(); printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count); }); }
也有人說這是個互斥鎖
NSConditionLock一樣實現了NSLocking協議,試驗過程當中發現性能很低。async
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lock]; if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObject:imageName]; } [lock unlock]; }
NSConditionLock也能夠像NSCondition同樣作多線程之間的任務等待調用,並且是線程安全的。
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lockWhenCondition:1]; //加鎖 if (imageNames.count>0) { imageName = [imageNames lastObject]; [imageNames removeObjectAtIndex:0]; } [lock unlockWithCondition:0]; //解鎖 } - (void)createImageName:(NSMutableArray *)imageNames{ [lock lockWhenCondition:0]; [imageNames addObject:@"0"]; [lock unlockWithCondition:1]; } #pragma mark - 多線程取出圖片後刪除 - (void)getImageNameWithMultiThread{ NSMutableArray *imageNames = [[NSMutableArray alloc]init]; dispatch_group_t dispatchGroup = dispatch_group_create(); __block double then, now; then = CFAbsoluteTimeGetCurrent(); for (int i=0; i<10000; i++) { dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){ [self getIamgeName:imageNames]; }); dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){ [self createImageName:imageNames]; }); } dispatch_group_notify(dispatchGroup, self.synchronizationQueue, ^(){ now = CFAbsoluteTimeGetCurrent(); printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count); }); }
有時候「加鎖代碼」中存在遞歸調用,遞歸開始前加鎖,遞歸調用開始後會重複執行此方法以致於反覆執行加鎖代碼最終形成死鎖,這個時候可使用遞歸鎖來解決。使用遞歸鎖能夠在一個線程中反覆獲取鎖而不形成死鎖,這個過程當中會記錄獲取鎖和釋放鎖的次數,只有最後二者平衡鎖才被最終釋放。
- (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; [lock lock]; if (imageNames.count>0) { imageName = [imageNames firstObject]; [imageNames removeObjectAtIndex:0]; [self getIamgeName:imageNames]; } [lock unlock]; } - (void)getImageNameWithMultiThread{ NSMutableArray *imageNames = [NSMutableArray new]; int count = 1024*10; for (int i=0; i<count; i++) { [imageNames addObject:[NSString stringWithFormat:@"%d",i]]; } dispatch_group_t dispatchGroup = dispatch_group_create(); __block double then, now; then = CFAbsoluteTimeGetCurrent(); dispatch_group_async(dispatchGroup, self.synchronizationQueue, ^(){ [self getIamgeName:imageNames]; }); dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){ now = CFAbsoluteTimeGetCurrent(); printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count); }); }
NSDistributedLock是MAC開發中的跨進程的分佈式鎖,底層是用文件系統實現的互斥鎖。NSDistributedLock沒有實現NSLocking協議,因此沒有lock方法,取而代之的是非阻塞的tryLock方法。
NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"]; while (![lock tryLock]) { sleep(1); } //do something [lock unlock];
#import <pthread.h>
聲明並初始化一個pthread_mutex_t的結構。使用pthread_mutex_lock和pthread_mutex_unlock函數。調用pthread_mutex_destroy來釋放該鎖的數據結構。
#import <pthread.h> @interface MYPOSIXViewController () { pthread_mutex_t mutex; //聲明pthread_mutex_t的結構 } @end @implementation MYPOSIXViewController - (void)dealloc{ pthread_mutex_destroy(&mutex); //釋放該鎖的數據結構 } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. pthread_mutex_init(&mutex, NULL); /** * 初始化 * */ } - (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; /** * 加鎖 */ pthread_mutex_lock(&mutex); if (imageNames.count>0) { imageName = [imageNames firstObject]; [imageNames removeObjectAtIndex:0]; } /** * 解鎖 */ pthread_mutex_unlock(&mutex); }
POSIX還能夠建立條件鎖,提供了和NSCondition同樣的條件控制,初始化互斥鎖同時使用pthread_cond_init來初始化條件數據結構,
// 初始化 int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr); // 等待(會阻塞) int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut); // 定時等待 int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mut, const struct timespec *abstime); // 喚醒 int pthread_cond_signal (pthread_cond_t *cond); // 廣播喚醒 int pthread_cond_broadcast (pthread_cond_t *cond); // 銷燬 int pthread_cond_destroy (pthread_cond_t *cond);
首先要提的是OSSpinLock已經出現了BUG,致使並不能徹底保證是線程安全的。
#import <libkern/OSAtomic.h>
#import <libkern/OSAtomic.h> @interface MYOSSpinLockViewController () { OSSpinLock spinlock; //聲明pthread_mutex_t的結構 } @end @implementation MYOSSpinLockViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. spinlock = OS_SPINLOCK_INIT; /** * 初始化 * */ } - (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; /** * 加鎖 */ OSSpinLockLock(&spinlock); if (imageNames.count>0) { imageName = [imageNames firstObject]; [imageNames removeObjectAtIndex:0]; } /** * 解鎖 */ OSSpinLockUnlock(&spinlock); } @end
dispatch_barrier_async/dispatch_barrier_sync在必定的基礎上也能夠作線程同步,會在線程隊列中打斷其餘線程執行當前任務,也就是說只有用在併發的線程隊列中才會有效,由於串行隊列原本就是一個一個的執行的,你打斷執行一個和插入一個是同樣的效果。兩個的區別是是否等待任務執行完成。
注意:若是在當前線程調用dispatch_barrier_sync打斷會發生死鎖。
@interface MYdispatch_barrier_syncViewController () { __block double then, now; } @end @implementation MYdispatch_barrier_syncViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } - (void)getIamgeName:(NSMutableArray *)imageNames{ NSString *imageName; if (imageNames.count>0) { imageName = [imageNames firstObject]; [imageNames removeObjectAtIndex:0]; }else{ now = CFAbsoluteTimeGetCurrent(); printf("thread_lock: %f sec\nimageNames count: %ld\n", now-then,imageNames.count); } } - (void)getImageNameWithMultiThread{ NSMutableArray *imageNames = [NSMutableArray new]; int count = 1024*11; for (int i=0; i<count; i++) { [imageNames addObject:[NSString stringWithFormat:@"%d",i]]; } then = CFAbsoluteTimeGetCurrent(); for (int i=0; i<count+1; i++) { //100來測試鎖有沒有正確的執行 dispatch_barrier_async(self.synchronizationQueue, ^{ [self getIamgeName:imageNames]; }); } }
@synchronized:適用線程很少,任務量不大的多線程加鎖 NSLock:其實NSLock並無想象中的那麼差,不知道你們爲何不推薦使用 dispatch_semaphore_t:使用信號來作加鎖,性能提高顯著 NSCondition:使用其作多線程之間的通訊調用不是線程安全的 NSConditionLock:單純加鎖性能很是低,比NSLock低不少,可是能夠用來作多線程處理不一樣任務的通訊調用 NSRecursiveLock:遞歸鎖的性能出奇的高,可是隻能做爲遞歸使用,因此限制了使用場景 NSDistributedLock:由於是MAC開發的,就不討論了 POSIX(pthread_mutex):底層的api,複雜的多線程處理建議使用,而且能夠封裝本身的多線程 OSSpinLock:性能也很是高,惋惜出現了線程問題 dispatch_barrier_async/dispatch_barrier_sync:測試中發現dispatch_barrier_sync比dispatch_barrier_async性能要高,真是大出意外