iOS多線程編程中,經常碰到多個線程訪問共同的一個資源,在線程相互交互的情況下,需要一些同步措施,來保證線程之間交互的時候是安全的。下面我們一起看一下學一下iOS的幾種常用的加鎖方式,希望對大家有所幫助!!!
- @synchronized
- NSLock對象鎖
- NSRecursiveLock遞歸鎖
- NSConditionLock條件鎖
- dispatch_semaphore 信號量實現加鎖(也就是GCD)
- OSSpinLock 與 os_unfair_lock
- pthread_mutex
介紹與使用
1.@synchronized
@synchronized關鍵字加鎖,互斥鎖,性能較差不推薦在項目中使用。
@synchronized(這里添加一個OC對象,一般使用self) { 這里寫要加鎖的代碼 }
注意點 1.加鎖的代碼要盡量少 2.添加的OC對象必須在多個線程中都是同一個對象 3.它的優點是不需要顯式的創建鎖對象,便可以實現鎖的機制。 4. @synchronized塊會隱式的添加異常處理例程來保護代碼,該處理例程會在異常拋出的時候就會自動 的釋放互斥鎖。如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對象。
下面我們以一個最經典的例子:賣票
//設置票的數量為5 _tickets = 5; //線程1 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); //線程2 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); - (void)saleTickets { while (1) { @synchronized(self) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票數= %ld, Thread:%@",_tickets,[NSThread currentThread]); } else { NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]); break; } } } }
2.NSLock
基本所有鎖的接口都是通過NSLocking協議定義的,定義了lock和unlock方法,通過這些方法獲取和釋放鎖。NSLock是對mutex普通鎖的封裝
下面還是以賣票的例子講述一下。
//設置票的數量為5 _tickets = 5; //創建鎖 _mutexLock = [[NSLock alloc] init]; //線程1 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); //線程2 dispatch_async(self.concurrentQueue, ^{ [self saleTickets]; }); - (void)saleTickets { while (1) { [NSThread sleepForTimeInterval:1]; //加鎖 [_mutexLock lock]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票數= %ld, Thread:%@",_tickets,[NSThread currentThread]); } else { NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]); break; } //解鎖 [_mutexLock unlock]; } }
3.NSRecursiveLock遞歸鎖
使用鎖比較容易犯的錯誤是在遞歸或者循環中造成死鎖。
如下代碼鎖會被多次lock,造成自己被阻塞。
//創建鎖 _mutexLock = [[NSLock alloc]init]; //線程1 dispatch_async(self.concurrentQueue, ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_mutexLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; TestMethod(value--); } [_mutexLock unlock]; }; TestMethod(5); });
如果把這個NSLock換成NSRecursiveLock,就可以解決問題。
NSRecursiveLock類定義的鎖,可以在同一線程多次lock,不會造成死鎖。
//創建鎖 _rsLock = [[NSRecursiveLock alloc] init]; //線程1 dispatch_async(self.concurrentQueue, ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_rsLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; TestMethod(value--); } [_rsLock unlock]; }; TestMethod(5); });
4.NSConditionLock條件鎖
NSMutableArray *products = [NSMutableArray array]; NSInteger HAS_DATA = 1; NSInteger NO_DATA = 0; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { [lock lockWhenCondition:NO_DATA]; [products addObject:[[NSObject alloc] init]]; NSLog(@"produce a product,總量:%zi",products.count); [lock unlockWithCondition:HAS_DATA]; sleep(1); } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { NSLog(@"wait for product"); [lock lockWhenCondition:HAS_DATA]; [products removeObjectAtIndex:0]; NSLog(@"custome a product"); [lock unlockWithCondition:NO_DATA]; } });
在線程1中的加鎖使用了lock,所以是不要條件的,也就鎖住了。但在unlock的使用整型條件,它可以開啟其他線程中正在等待鑰匙的臨界池,當線程1循環到一次的時候,打開了線程2的阻塞。
NSCoditionLock中lock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,具體使用根據需求來區分。
NSCoditionLock 是對NSCodition的進一步封裝,可以設置具體的條件值,而NSCodition是對mutex和cond的封裝---看本篇博客7.3 條件鎖
5.dispatch_semaphore信號量實現加鎖
dispatch_semaphore_t signal = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(signal, overTime); NSLog(@"需要線程同步的操作1 開始"); sleep(2); NSLog(@"需要線程同步的操作1 結束"); dispatch_semaphore_signal(signal); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(signal, overTime); NSLog(@"需要線程同步的操作2"); dispatch_semaphore_signal(signal); });
dispatch_semaphore是GCD用於同步的方式,與之相關的共有三個函數,dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。
(1)dispatch_semaphore_create的聲明為:
dispatch_semaphore_t dispatch_semaphore_create(long value);
傳入的參數是long類型,輸出一個dispatch_semaphore_t類型值為Value的信號量(value傳入值不能小於0,否則會報錯NULL)
(2)dispatch_semaphore_signal聲明為下面:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
這個方法會使dsema加1;
(3)dispatch_semaphore_wait的聲明為下面:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
這個方法會使dsema減1。
整個邏輯如下:
如果dsema信號量值為大於0,該函數所在線程就會繼續執行下面的語句,並將信號量的減去1;如果dsema為0時,函數就會阻塞當前的線程,如果等待的期間發現dsema的值被dispatch_semaphore_signal加1了,並且該函數得到了信號量,那么繼續向下執行,並將信號量減1,如果等待期間沒有獲得信號量或者值一直為0,那么等到timeout,所處的線程也會自動執行下面的代碼。
dispatch_semaphore,當信號量為1時,可以作為鎖使用。如果沒有出現等待的情況,它的性能比pthread_mutex還要高,當如果有等待情況的時候,性能就會下降很多,相比OSSpinLock(暫不講解),它的優勢在於等待的時侯不會消耗CPU資源。
針對上面代碼,發現如果超時時間overTime>2,可完成同步操作,反之,在線程1還沒有執行完的情況下,此時超時了,將自動執行下面的代碼。
上面代碼執行結果:
2018-09-18 15:40:52.324 SafeMultiThread[35945:579032] 需要線程同步的操作1 開始 2018-09-18 15:40:52.325 SafeMultiThread[35945:579032] 需要線程同步的操作1 結束 2018-09-18 15:40:52.326 SafeMultiThread[35945:579033] 需要線程同步的操作2
如果將overTime<2s的時候,執行為
2018-09-18 15:40:52.049 SafeMultiThread[30834:434334] 需要線程同步的操作1 開始 2018-09-18 15:40:52.554 SafeMultiThread[30834:434332] 需要線程同步的操作2 2018-09-18 15:40:52.054 SafeMultiThread[30834:434334] 需要線程同步的操作1 結束
6.OSSpinLock自旋鎖與os_unfair_lock
6.1 OSSpinLock
OSSpinLock叫做“自旋鎖”,自旋鎖的線程會處於忙等(busy-wait)狀態,一直占用着CPU資源
但是目前是不再安全了,在iOS 10之后棄用啦,如果等待鎖的線程優先級較高,它會一直占用着CPU資源,優先級低的線程就無法釋放鎖。
在使用的過程中需要導入頭文件#import <libkern/OSAtomic.h>
//初始化 OSSpinLock lock = OS_SPINLOCK_INIT; //嘗試加鎖(如果需要等待就不加鎖,直接返回false;如果不需要等待就加鎖,返回True) bool result = OSSpinLockTry(&lock); //加鎖 OSSpinLockLock(&lock); //解鎖 OSSpinLockUnlock(&lock)
6.2 os_unfair_lock互斥鎖
os_unfair_lock用於取代不安全的OSSpinLock,從iOS10開始才支持。從底層調用看,等待os_unfair_lock鎖的線程會處於休眠狀態,並非忙等
os_unfair_lock需要導入頭文件#import<os/lock.h>
//初始化 os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //嘗試加鎖 os_unfair_lock_trylock(&lock); //加鎖 os_unfair_lock_lock(&lock); //解鎖 os_unfair_lock_unlock(&lock);
7.pthread_mutex
mutex叫做“互斥鎖”,等待鎖的線程處於休眠狀態
需要導入頭文件#import <pthread.h>
7.1
//初始化鎖的屬性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); //初始化鎖 pthread_mutex_t mutex; pthread_mutex_init(&mutex, &attr); //嘗試加鎖 pthread_mutex_trylock(&mutex); //加鎖 pthread_mutex_lock(&mutex); //解鎖 pthread_mutex_unlock(&mutex); //銷毀相關資源 --pthread_mutex在對象類釋放的時候要銷毀,其他鎖無此情況 pthread_mutexattr_destroy(&attr); pthread_mutex_destroy(&mutex);
7.2 pthread_mutex-遞歸鎖
//初始化鎖的屬性 pthread_mutexattr_t attr; pthread_mutexattr_t_int(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);//遞歸鎖 //初始化鎖 pthread_mutex_t mutex; pthread_mutex_init(&mutex, &attr);
7.3 pthread_mutex-條件
#import "MutexDemo3.h" #import <pthread.h> @interface MutexDemo3() @property (assign, nonatomic) pthread_mutex_t mutex; @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; @end @implementation MutexDemo3 - (instancetype)init { if (self = [super init]) { // 初始化屬性 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 初始化鎖 pthread_mutex_init(&_mutex, &attr); // 銷毀屬性 pthread_mutexattr_destroy(&attr); // 初始化條件 pthread_cond_init(&_cond, NULL); self.data = [NSMutableArray array]; } return self; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // 生產者-消費者模式 // 線程1 // 刪除數組中的元素 - (void)__remove { pthread_mutex_lock(&_mutex); NSLog(@"__remove - begin"); if (self.data.count == 0) { // 等待 pthread_cond_wait(&_cond, &_mutex); } [self.data removeLastObject]; NSLog(@"刪除了元素"); pthread_mutex_unlock(&_mutex); } // 線程2 // 往數組中添加元素 - (void)__add { pthread_mutex_lock(&_mutex); sleep(1); [self.data addObject:@"Test"]; NSLog(@"添加了元素"); // 信號 pthread_cond_signal(&_cond); // 廣播 // pthread_cond_broadcast(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } @end
補充:自旋鎖、互斥鎖比較
1. 什么情況下使用自旋鎖比較划算?(OSSpinLock,但被os_unfair_lock取代,但是os_unfair_lock不是自旋鎖)
- 預計線程等待鎖的時間很短
- 加鎖的代碼(臨界區)經常被調用,但競爭情況很少發生
- CPU資源不緊張
- 多核處理器
2. 什么情況下使用互斥鎖比較划算?(pthread_mutex, NSLock等)
- 預計線程等待鎖的時間較長
- 單核處理器
- 臨界區有IO操作
- 臨界區代碼復雜或者循環量大
- 臨界區競爭非常激烈
以上就是自己在開發中所經常使用到的加鎖方式,希望對大家有所幫助!!!