iOS多線程編程中,經(jīng)常碰到多個線程訪問共同的一個資源,在線程相互交互的情況下,需要一些同步措施,來保證線程之間交互的時候是安全的。下面我們一起看一下學(xué)一下iOS的幾種常用的加鎖方式,希望對大家有所幫助!!!
@synchronized
NSLock對象鎖
NSRecursiveLock遞歸鎖
NSConditionLock條件鎖
dispatch_semaphore 信號量實現(xiàn)加鎖(也就是GCD)
OSSpinLock 與 os_unfair_lock
pthread_mutex
介紹與使用
1.@synchronized
@synchronized關(guān)鍵字加鎖,互斥鎖,性能較差不推薦在項目中使用。
@synchronized(這里添加一個OC對象,一般使用self) {
這里寫要加鎖的代碼
}
注意點
1.加鎖的代碼要盡量少
2.添加的OC對象必須在多個線程中都是同一個對象
3.它的優(yōu)點是不需要顯式的創(chuàng)建鎖對象,便可以實現(xiàn)鎖的機制。
- @synchronized塊會隱式的添加異常處理例程來保護(hù)代碼,該處理例程會在異常拋出的時候就會自動 的釋放互斥鎖。如果不想讓隱式的異常處理例程帶來額外的開銷,你可以考慮使用鎖對象。
下面我們以一個最經(jīng)典的例子:賣票
//設(shè)置票的數(shù)量為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(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
}
}
}
2.NSLock
基本所有鎖的接口都是通過NSLocking協(xié)議定義的,定義了lock和unlock方法,通過這些方法獲取和釋放鎖。NSLock是對mutex普通鎖的封裝
下面還是以賣票的例子講述一下。
//設(shè)置票的數(shù)量為5
_tickets = 5;
//創(chuàng)建鎖
_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(@"剩余票數(shù)= %ld, Thread:%@",_tickets,[NSThread currentThread]);
} else {
NSLog(@"票賣完了 Thread:%@",[NSThread currentThread]);
break;
}
//解鎖
[_mutexLock unlock];
}
}
3.NSRecursiveLock遞歸鎖
使用鎖比較容易犯的錯誤是在遞歸或者循環(huán)中造成死鎖。
如下代碼鎖會被多次lock,造成自己被阻塞。
//創(chuàng)建鎖
_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,不會造成死鎖。
//創(chuàng)建鎖
_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的使用整型條件,它可以開啟其他線程中正在等待鑰匙的臨界池,當(dāng)線程1循環(huán)到一次的時候,打開了線程2的阻塞。
NSCoditionLock中l(wèi)ock,lockWhenCondition:與unlock,unlockWithCondition:是可以隨意組合的,具體使用根據(jù)需求來區(qū)分。
NSCoditionLock 是對NSCodition的進(jìn)一步封裝,可以設(shè)置具體的條件值,而NSCodition是對mutex和cond的封裝---看本篇博客7.3 條件鎖
5.dispatch_semaphore信號量實現(xiàn)加鎖
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 結(jié)束");
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用于同步的方式,與之相關(guān)的共有三個函數(shù),dispatch_semaphore_wait,dispatch_semaphore_signal,dispatch_semaphore_create。
(1)dispatch_semaphore_create的聲明為:
dispatch_semaphore_t dispatch_semaphore_create(long value);
傳入的參數(shù)是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,該函數(shù)所在線程就會繼續(xù)執(zhí)行下面的語句,并將信號量的減去1;如果dsema為0時,函數(shù)就會阻塞當(dāng)前的線程,如果等待的期間發(fā)現(xiàn)dsema的值被dispatch_semaphore_signal加1了,并且該函數(shù)得到了信號量,那么繼續(xù)向下執(zhí)行,并將信號量減1,如果等待期間沒有獲得信號量或者值一直為0,那么等到timeout,所處的線程也會自動執(zhí)行下面的代碼。
dispatch_semaphore,當(dāng)信號量為1時,可以作為鎖使用。如果沒有出現(xiàn)等待的情況,它的性能比pthread_mutex還要高,當(dāng)如果有等待情況的時候,性能就會下降很多,相比OSSpinLock(暫不講解),它的優(yōu)勢在于等待的時侯不會消耗CPU資源。
針對上面代碼,發(fā)現(xiàn)如果超時時間overTime>2,可完成同步操作,反之,在線程1還沒有執(zhí)行完的情況下,此時超時了,將自動執(zhí)行下面的代碼。
上面代碼執(zhí)行結(jié)果:
2018-09-18 15:40:52.324 SafeMultiThread[35945:579032] 需要線程同步的操作1 開始
2018-09-18 15:40:52.325 SafeMultiThread[35945:579032] 需要線程同步的操作1 結(jié)束
2018-09-18 15:40:52.326 SafeMultiThread[35945:579033] 需要線程同步的操作2
如果將overTime<2s的時候,執(zhí)行為
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 結(jié)束
6.OSSpinLock自旋鎖與os_unfair_lock
6.1 OSSpinLock
OSSpinLock叫做“自旋鎖”,自旋鎖的線程會處于忙等(busy-wait)狀態(tài),一直占用著CPU資源
但是目前是不再安全了,在iOS 10之后棄用啦,如果等待鎖的線程優(yōu)先級較高,它會一直占用著CPU資源,優(yōu)先級低的線程就無法釋放鎖。
在使用的過程中需要導(dǎo)入頭文件#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開始才支持。從底層調(diào)用看,等待os_unfair_lock鎖的線程會處于休眠狀態(tài),并非忙等
os_unfair_lock需要導(dǎo)入頭文件#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叫做“互斥鎖”,等待鎖的線程處于休眠狀態(tài)
需要導(dǎo)入頭文件#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);
//銷毀相關(guān)資源 --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];
}
// 生產(chǎn)者-消費者模式
// 線程1
// 刪除數(shù)組中的元素
-
(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
// 往數(shù)組中添加元素
-
(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