一蜜氨、線程安全問題
在單線程的情形下黔姜,任務(wù)依次串行執(zhí)行是不存在線程安全問題的墩剖。在單線程的情形下,如果多線程都是訪問共享資源而不去修改共享資源也可以保證線程安全,比如:設(shè)置只讀屬性的全局變量。線程不安全是由于多線程訪問造成的凤薛,是由于多線程訪問和修改共享資源而引起不可預(yù)測的結(jié)果。而線程鎖可以有效的解決線程安全問題诞仓,大致過程如下圖:
iOS 多線程開發(fā)中為保證線程安全而常用的幾種鎖:NSLock
缤苫、dispatch_semaphore
、NSCondition
狂芋、NSRecursiveLock
榨馁、NSConditionLock
、@synchronized
帜矾,這幾種鎖各有優(yōu)點,適用于不同的場景屑柔,下面我們就來依次介紹一下屡萤。
二、iOS中的鎖
1. NSLock
NSLock 是OC層封裝底層線程操作來實現(xiàn)的一種鎖掸宛,繼承NSLocking協(xié)議死陆,在此我們不討論各種鎖的實現(xiàn)細節(jié),因為基本用不到。NSLock使用非常簡單:
NSLock *lock = [NSLock alloc] init];
// 加鎖
[lock lock];
/*
* 被加鎖的代碼區(qū)間
*/
// 解鎖
[lock Unlock];
我們以車站購票為例子措译,多個窗口同時售票别凤,每個窗口有人循環(huán)購票:
// 定義NSLock變量
@property (nonatomic, strong) NSLock *lock;
// 實例化
_lock = [[NSLock alloc] init];
/*******************************************************************************/
// 調(diào)用測試方法
dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
[self testNSLock];
});
}
}
/*******************************************************************************/
// 測試方法
- (void)testNSLock {
while (1) {
[_lock lock];
if (_ticketCount > 0) {
_ticketCount --;
NSLog(@"--->> %@已購票1張,剩余%ld張", [NSThread currentThread], (long)_ticketCount);
}
else {
[_lock unlock];
return;
}
[_lock unlock];
sleep(0.2);
}
}
2. dispatch_semaphore
dispatch_semaphore 是 GCD 提供的领虹,使用信號量來控制并發(fā)線程的數(shù)量(可同時進入并執(zhí)行加鎖代碼塊的線程的數(shù)量)规哪,相關(guān)的三個函數(shù):
// 創(chuàng)建信號量
dispatch_semaphore_create(long value);
//等待信號
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
發(fā)送信號
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
//! 定義信號量semaphore
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
//! 實例化
_semaphore = dispatch_semaphore_create(1);
/*******************************************************************************/
// 調(diào)用測試方法
- (void)multiThread {
dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i=0; i<2; i++) {
dispatch_async(queue, ^{
[self testDispatchSemaphore:i];
});
}
}
/*******************************************************************************/
// 測試方法
- (void)testDispatchSemaphore:(NSInteger)num {
while (1) {
// 參數(shù)1為信號量;參數(shù)2為超時時間塌衰;ret為返回值
//dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
long ret = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.21*NSEC_PER_SEC)));
if (ret == 0) {
if (_ticketCount > 0) {
NSLog(@"%d 窗口 賣了第%d張票", (int)num, (int)_ticketCount);
_ticketCount --;
}
else {
dispatch_semaphore_signal(_semaphore);
NSLog(@"%d 賣光了", (int)num);
break;
}
[NSThread sleepForTimeInterval:0.2];
dispatch_semaphore_signal(_semaphore);
}
else {
NSLog(@"%d %@", (int)num, @"超時了");
}
[NSThread sleepForTimeInterval:0.2];
}
}
當?shù)谝桓鲄?shù)semaphore取值為1時诉稍,dispatch_semaphore_wait(semaphore, timeout)與dispatch_semaphore_signal(signal)成對出現(xiàn),所達到的效果就跟NSLock中的lock和unlock是一樣的最疆。區(qū)別在于當semaphore取值為n時杯巨,則可以有n個線程同時訪問被保護的臨界區(qū),即可以控制多個線程并發(fā)努酸。第二個參數(shù)為dispatch_time_t類型服爷,如果直接輸入一個非dispatch_time_t的值會導致dispatch_semaphore_wait方法偶爾返回非0值。
3. NSCondition
NSCondition 常用于生產(chǎn)者-消費者模式获诈,它繼承于NSLocking協(xié)議仍源,同樣有l(wèi)ock和unlock方法。條件變量有點像信號量烙荷,提供了線程阻塞與信號機制镜会,因此可以用來阻塞某個線程,并等待數(shù)據(jù)就緒终抽,再喚醒線程戳表。
NSCondition *lock = [[NSCondition alloc] init];
//線程A
[lock lock];
[lock wait]; // 線程被掛起
[lock unlock];
//線程2
sleep(1);//以保證讓線程2的代碼后執(zhí)行
[lock lock];
[lock signal]; // 喚醒線程1
[lock unlock];
我們執(zhí)行了兩次for循環(huán),起了兩批新線程昼伴,一批來add數(shù)據(jù)匾旭,另一批來remove數(shù)據(jù)。其中add數(shù)據(jù)方法加鎖圃郊,remove數(shù)據(jù)方法也加了鎖:
// 定義變量
@property (nonatomic, strong) NSCondition *condition;
// 實例化
_condition = [[NSCondition alloc] init];
/*******************************************************************************/
// 調(diào)用測試方法
- (void)multiThread {
dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
[self testNSConditionAdd];
});
}
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
[self testNSConditionRemove];
});
}
}
/*******************************************************************************/
// 測試方法
- (void)testNSConditionAdd {
[_condition lock];
// 生產(chǎn)數(shù)據(jù)
NSObject *object = [NSObject new];
[_ticketsArr addObject:object];
NSLog(@"--->>%@ add", [NSThread currentThread]);
[_condition signal];
[_condition unlock];
}
- (void)testNSConditionRemove {
[_condition lock];
// 消費數(shù)據(jù)
if (!_ticketsArr.count) {
NSLog(@"--->> wait");
[_condition wait];
}
[_ticketsArr removeObjectAtIndex:0];
NSLog(@"--->>%@ remove", [NSThread currentThread]);
[_condition unlock];
}
4. NSConditionLock
NSConditionLock 為條件鎖价涝,lockWhenCondition:方法是當condition參數(shù)與初始化時候的 condition 相等時才可加鎖。而unlockWithCondition:方法并不是當 Condition 符合條件時才解鎖持舆,而是解鎖之后色瘩,修改 Condition 的值。NSConditionLock 借助 NSCondition 來實現(xiàn)逸寓,它的本質(zhì)就是一個生產(chǎn)者-消費者模型居兆。“條件被滿足”可以理解為生產(chǎn)者提供了新的內(nèi)容NSConditionLock 的內(nèi)部持有一個 NSCondition 對象竹伸,以及 _condition_value 屬性泥栖,在初始化時就會對這個屬性進行賦值:
// 設(shè)置條件
#define CONDITION_NO_DATA 100
#define CONDITION_HAS_DATA 101
/*******************************************************************************/
// 初始化條件鎖對象
@property (nonatomic, strong) NSConditionLock *conditionLock;
// 實例化
_conditionLock = [[NSConditionLock alloc] initWithCondition:CONDITION_NO_DATA];
/*******************************************************************************/
// 調(diào)用測試方法
- (void)multiThread {
dispatch_queue_t queue = dispatch_queue_create("QiMultiThreadSafeQueue", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
[self testNSConditionLockAdd];
});
}
for (NSInteger i=0; i<10; i++) {
dispatch_async(queue, ^{
[self testNSConditionLockRemove];
});
}
}
/*******************************************************************************/
// 測試方法
- (void)testNSConditionLockAdd {
// 滿足CONDITION_NO_DATA時,加鎖
[_conditionLock lockWhenCondition:CONDITION_NO_DATA];
// 生產(chǎn)數(shù)據(jù)
NSObject *object = [NSObject new];
[_ticketsArr addObject:object];
NSLog(@"---->>%@ add", [NSThread currentThread]);
[_condition signal];
// 有數(shù)據(jù),解鎖并設(shè)置條件
[_conditionLock unlockWithCondition:CONDITION_HAS_DATA];
}
- (void)testNSConditionLockRemove {
// 有數(shù)據(jù)時吧享,加鎖
[_conditionLock lockWhenCondition:CONDITION_HAS_DATA];
// 消費數(shù)據(jù)
if (!_ticketsArr.count) {
NSLog(@"---->> wait");
[_condition wait];
}
[_ticketsArr removeObjectAtIndex:0];
NSLog(@"---->>%@ remove", [NSThread currentThread]);
//3. 沒有數(shù)據(jù)魏割,解鎖并設(shè)置條件
[_conditionLock unlockWithCondition:CONDITION_NO_DATA];
}
5. NSRecursiveLock
顧名思義,NSRecursiveLock定義的是一個遞歸鎖钢颂,這個鎖可以被同一線程多次請求钞它,而不會引起死鎖。這主要是用在循環(huán)或遞歸操作中甸陌。NSRecursiveLock在識別到遞歸時须揣,只加1次鎖,在遞歸返回時也只解鎖1次钱豁。
// 初始化鎖對象
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;
_recursiveLock = [[NSRecursiveLock alloc] init];
/*******************************************************************************/
// 加鎖的遞歸方法
- (void)testNSRecursiveLock:(NSInteger)tag {
[_recursiveLock lock];
if (tag > 0) {
[self testNSRecursiveLock:tag - 1];
NSLog(@"--->> %ld", (long)tag);
}
[_recursiveLock unlock];
}
6. @synchronized
@synchronized是一個 OC 層面的鎖耻卡,非常簡單易用。參數(shù)需要傳一個 OC 對象牲尺,它實際上是把這個對象當做鎖的唯一標識卵酪。使用時直接將加鎖的代碼區(qū)間放入花括號中即可,但是它的缺點也顯而易見谤碳,雖然易用溃卡,但是沒有之上介紹幾個鎖的復雜功能
- (void)testSynchronized {
@synchronized (self) {
if (_ticketCount > 0) {
_ticketCount --;
NSLog(@"--->> %@已購票1張,剩余%ld張", [NSThread currentThread], (long)_ticketCount);
}
}
}
原子操作
原子操作是指不可打斷的操作蜒简,也就是說線程在執(zhí)行操作過程中瘸羡,不會被操作系統(tǒng)掛起,而是一定會執(zhí)行完搓茬。如文章開頭出圖中17+1 = 18這個動作犹赖,在整個運算過程中,就屬于一個原子操作卷仑。
變量屬性Property中的原子定義
一般我們定義一個變量 @property (nonatomic, strong) NSMutableArray *ticketsArr;
nonatomic:非原子屬性峻村,不會為setter方法加鎖,適合內(nèi)存小的移動設(shè)備锡凝;
atomic:原子屬性粘昨,默認為setter方法加鎖(默認就是atomic),線程安全窜锯。
PS: 在iOS開發(fā)過程中张肾,一般都將屬性聲明為nonatomic,盡量避免多線程搶奪同一資源锚扎,盡量將加鎖等資源搶奪業(yè)務(wù)交給服務(wù)器捌浩。
本文參考了以下文章:
- https://www.cnblogs.com/crash-wu/p/4806499.html
-
https://blog.csdn.net/abc649395594/article/details/52747864
非常感謝!
關(guān)注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS 多線程之GCD
iOS 多線程之NSOperation
iOS 多線程之NSThread
iOS Winding Rules 纏繞規(guī)則
iOS 簽名機制
iOS 掃描二維碼/條形碼
奇舞周刊