多執行緒的安全隱患
一塊資源可能會被多個執行緒共享,也就是多個執行緒可能會訪問同一塊資源;當多個執行緒訪問同一塊資源時,很容易引發資料錯亂和資料安全問題### 問題案例
賣票和存錢取錢的兩個案例,具體見下面代碼
@interface BaseDemo: NSObject
- (void)moneyTest;
- (void)ticketTest;
#pragma mark - 暴露給子類去使用
- (void)__saveMoney;
- (void)__drawMoney;
- (void)__saleTicket;
@end
@interface BaseDemo()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@end
@implementation BaseDemo
/**
存錢、取錢演示
*/
- (void)moneyTest
{
self.money = 100;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self __saveMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self __drawMoney];
}
});
}
/**
存錢
*/
- (void)__saveMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存50,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
取錢
*/
- (void)__drawMoney
{
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取20,還剩%d元 - %@", oldMoney, [NSThread currentThread]);
}
/**
賣1張票
*/
- (void)__saleTicket
{
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"還剩%d張票 - %@", oldTicketsCount, [NSThread currentThread]);
}
/**
賣票演示
*/
- (void)ticketTest
{
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self __saleTicket];
}
});
}
@end
@interface ViewController ()
@property (strong, nonatomic) BaseDemo *demo;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
BaseDemo *demo = [[BaseDemo alloc] init];
[demo ticketTest];
[demo moneyTest];
}
@end
解決方案是使用執行緒同步技術(同步,就是協同步調,按預定的先后次序進行,常見的方案就是加鎖


執行緒同步方案
iOS中的執行緒同步方案有以下這些
- OSSpinLock- os_unfair_lock- pthread_mutex- dispatch_semaphore- dispatch_queue(DISPATCH_QUEUE_SERIAL)- NSLock- NSRecursiveLock- NSCondition- NSConditionLock- @synchronized#### OSSpinLock
OSSpinLock叫做”自旋鎖”,等待鎖的執行緒會處于忙等(busy-wait)狀態,一直占用著CPU資源用OSSpinLock來解決上述示例問題
@interface OSSpinLockDemo: BaseDemo
@end
#import "OSSpinLockDemo.h"
#import <libkern/OSAtomic.h>
@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock moneyLock;
// @property (assign, nonatomic) OSSpinLock ticketLock;
@end
@implementation OSSpinLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_SPINLOCK_INIT;
// self.ticketLock = OS_SPINLOCK_INIT;
}
return self;
}
- (void)__drawMoney
{
OSSpinLockLock(&_moneyLock);
[super __drawMoney];
OSSpinLockUnlock(&_moneyLock);
}
- (void)__saveMoney
{
OSSpinLockLock(&_moneyLock);
[super __saveMoney];
OSSpinLockUnlock(&_moneyLock);
}
- (void)__saleTicket
{
// 不用屬性,用一個靜態變數也可以
static OSSpinLock ticketLock = OS_SPINLOCK_INIT;
OSSpinLockLock(&ticketLock);
[super __saleTicket];
OSSpinLockUnlock(&ticketLock);
}
@end
static的問題
上面的ticketLock也可以用static來修飾作為內部靜態變數來使用
#define OS_SPINLOCK_INIT 0
由于OS_SPINLOCK_INIT就是0,所以才可以用static來修飾;static只能在編譯時賦值一個確定值,不能動態賦予一個函式值
// 這樣賦值一個函式回傳值是會報錯的
static OSSpinLock ticketLock = [NSString stringWithFormat:@"haha"];
OSSpinLock的問題
OSSpinLock現在已經不再安全,可能會出現優先級反轉問題
由于多執行緒的本質是在不同執行緒之間進行來回的調度,每個執行緒可能對應分配的資源優先級不同;如果優先級低的執行緒先進行了加鎖并準備執行代碼,這時優先級高的執行緒就會在外面回圈等待加鎖;但因為其優先級高,所以CPU可能會大量的給其分配任務,那么就沒辦法處理優先級低的執行緒;優先級低的執行緒就無法繼續往下執行代碼,那么也就沒辦法解鎖,所以又會變成了互相等待的局面,造成死鎖,這也是蘋果現在廢棄了OSSpinLock的原因
解決辦法
用嘗試加鎖OSSpinLockTry來替換OSSpinLockLock,如果沒有加鎖才會進到判斷里執行代碼并加鎖,避免了因上鎖了一直在回圈等待的問題
// 用賣票的函式來舉例,其他幾個加鎖的方法也是同樣
- (void)__saleTicket
{
if (OSSpinLockTry(&_ticketLock)) {
[super __saleTicket];
OSSpinLockUnlock(&_ticketLock);
}
}
通過匯編來分析
我們通過斷點來分析加鎖之后做了什么
我們在賣票的加鎖代碼處打上斷點,并通過轉匯編的方式一步步呼叫分析

轉成匯編后呼叫OSSpinLockLock

內部會呼叫_OSSpinLockLockSlow

核心部分,在_OSSpinLockLockSlow會進行比較,然后執行到斷點處又會再次跳回0x7fff5e73326f再次執行代碼


所以通過匯編底層執行邏輯,我們能看出OSSpinLock是會不斷回圈去呼叫判斷的,只有解鎖之后才會往下執行代碼
鎖的等級
OSSpinLock自旋鎖是高等級的鎖(High-level lock),因為會一直回圈呼叫
os_unfair_lock
蘋果現在用os_unfair_lock用于取代不安全的OSSpinLock ,從iOS10開始才支持
從底層呼叫看,等待os_unfair_lock鎖的執行緒會處于休眠狀態,并非忙等
修改示例代碼如下
#import "BaseDemo.h"
@interface OSUnfairLockDemo: BaseDemo
@end
#import "OSUnfairLockDemo.h"
#import <os/lock.h>
@interface OSUnfairLockDemo()
@property (assign, nonatomic) os_unfair_lock moneyLock;
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end
@implementation OSUnfairLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.moneyLock = OS_UNFAIR_LOCK_INIT;
self.ticketLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}
- (void)__saleTicket
{
os_unfair_lock_lock(&_ticketLock);
[super __saleTicket];
os_unfair_lock_unlock(&_ticketLock);
}
- (void)__saveMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __saveMoney];
os_unfair_lock_unlock(&_moneyLock);
}
- (void)__drawMoney
{
os_unfair_lock_lock(&_moneyLock);
[super __drawMoney];
os_unfair_lock_unlock(&_moneyLock);
}
@end
如果不寫os_unfair_lock_unlock,那么所有的執行緒都會卡在os_unfair_lock_lock進入睡眠,不會再繼續執行代碼,這種情況叫做死鎖
通過匯編來分析
我們也通過斷點來分析加鎖之后做了什么
首先會呼叫os_unfair_lock_lock

然后會呼叫os_unfair_lock_lock_slow

然后在os_unfair_lock_lock_slow中會執行__ulock_wait


核心部分,代碼執行到syscall會直接跳出斷點,不再執行代碼,也就是進入了睡眠


所以通過匯編底層執行邏輯,我們能看出os_unfair_lock一旦進行了加鎖,就會直接進入休眠,等待解鎖后喚醒再繼續執行代碼,也由此可以認為os_unfair_lock是互斥鎖
syscall的呼叫可以理解為系統級別的呼叫進入睡眠,會直接卡住執行緒,不再執行代碼
鎖的等級
我們進到os_unfair_lock的頭檔案lock.h,可以看到注釋說明os_unfair_lock是一個低等級的鎖(Low-level lock),因為一旦發現加鎖后就會自動進入睡眠

pthread_mutex
互斥鎖
mutex叫做”互斥鎖”,等待鎖的執行緒會處于休眠狀態
使用代碼如下
@interface MutexDemo: BaseDemo
@end
#import "MutexDemo.h"
#import <pthread.h>
@interface MutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
@end
@implementation MutexDemo
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 靜態初始化
// 需要在定義這個變數時給予值才可以這么寫
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// // 初始化屬性
// pthread_mutexattr_t attr;
// pthread_mutexattr_init(&attr);
// pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// // 初始化鎖
// pthread_mutex_init(mutex, &attr);
// &attr傳NULL默認就是PTHREAD_MUTEX_DEFAULT
pthread_mutex_init(mutex, NULL);
}
- (instancetype)init
{
if (self = [super init]) {
[self __initMutex:&_ticketMutex];
[self __initMutex:&_moneyMutex];
}
return self;
}
- (void)__saleTicket
{
pthread_mutex_lock(&_ticketMutex);
[super __saleTicket];
pthread_mutex_unlock(&_ticketMutex);
}
- (void)__saveMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __saveMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)__drawMoney
{
pthread_mutex_lock(&_moneyMutex);
[super __drawMoney];
pthread_mutex_unlock(&_moneyMutex);
}
- (void)dealloc
{
// 物件銷毀時呼叫
pthread_mutex_destroy(&_moneyMutex);
pthread_mutex_destroy(&_ticketMutex);
}
pthread_mutex_t實際就是pthread_mutex *型別
遞回鎖
當屬性設定為PTHREAD_MUTEX_RECURSIVE時,就可以作為遞回鎖來使用
遞回鎖允許同一個執行緒對一把鎖進行重復加鎖;多個執行緒不可以用遞回鎖
- (void)__initMutex:(pthread_mutex_t *)mutex
{
// 初始化屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化鎖
pthread_mutex_init(mutex, &attr);
}
- (void)otherTest
{
pthread_mutex_lock(&_mutex);
NSLog(@"%s", __func__);
static int count = 0;
if (count < 10) {
count++;
[self otherTest];
}
pthread_mutex_unlock(&_mutex);
}
根據條件來進行加鎖
我們可以設定一定的條件來選擇執行緒之間的呼叫進行加鎖解鎖,示例如下
@interface MutexDemo()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation MutexDemo
- (instancetype)init
{
if (self = [super init]) {
// 初始化鎖
pthread_mutex_init(&_mutex, NULL);
// 初始化條件
pthread_cond_init(&_cond, NULL);
self.data = https://www.cnblogs.com/funkyRay/p/[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
通過匯編來分析
我們通過斷點來分析加鎖之后做了什么
首先會執行pthread_mutex_lock

然后會執行pthread_mutex_firstfit_lock_slow


然后會執行pthread_mutex_firstfit_lock_wait


然后會執行__psynch_mutexwait

核心部分,在__psynch_mutexwait里,代碼執行到syscall會直接跳出斷點,不再執行代碼,也就是進入了睡眠


所以pthread_mutex和os_unfair_lock一樣,都是在加鎖之后會進入到睡眠
鎖的等級
pthread_mutex和os_unfair_lock一樣,都是低等級的鎖(Low-level lock)
NSLock
NSLock是對mutex普通鎖的封裝
NSLock遵守了<NSLocking>協議,支持以下兩個方法
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
其他常用方法
// 嘗試解鎖
- (BOOL)tryLock;
// 設定一個時間等待加鎖,時間到了如果還不能成功加鎖就回傳NO
- (BOOL)lockBeforeDate:(NSDate *)limit;
具體使用看下面代碼
@interface NSLockDemo: BaseDemo
@end
@interface NSLockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@property (strong, nonatomic) NSLock *moneyLock;
@end
@implementation NSLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.ticketLock = [[NSLock alloc] init];
self.moneyLock = [[NSLock alloc] init];
}
return self;
}
- (void)__saleTicket
{
[self.ticketLock lock];
[super __saleTicket];
[self.ticketLock unlock];
}
- (void)__saveMoney
{
[self.moneyLock lock];
[super __saveMoney];
[self.moneyLock unlock];
}
- (void)__drawMoney
{
[self.moneyLock lock];
[super __drawMoney];
[self.moneyLock unlock];
}
@end
分析底層實作
由于NSLock是不開源的,我們可以通過GNUstep Base來分析具體實作
找到NSLock.m可以看到initialize初始化方法里是創建的pthread_mutex_t物件,所以可以確定NSLock是對pthread_mutex的面向物件的封裝
@implementation NSLock
+ (id) allocWithZone: (NSZone*)z
{
if (self == baseLockClass && YES == traceLocks)
{
return class_createInstance(tracedLockClass, 0);
}
return class_createInstance(self, 0);
}
+ (void) initialize
{
static BOOL beenHere = NO;
if (beenHere == NO)
{
beenHere = YES;
/* Initialise attributes for the different types of mutex.
* We do it once, since attributes can be shared between multiple
* mutexes.
* If we had a pthread_mutexattr_t instance for each mutex, we would
* either have to store it as an ivar of our NSLock (or similar), or
* we would potentially leak instances as we couldn't destroy them
* when destroying the NSLock. I don't know if any implementation
* of pthreads actually allocates memory when you call the
* pthread_mutexattr_init function, but they are allowed to do so
* (and deallocate the memory in pthread_mutexattr_destroy).
*/
pthread_mutexattr_init(&attr_normal);
pthread_mutexattr_settype(&attr_normal, PTHREAD_MUTEX_NORMAL);
pthread_mutexattr_init(&attr_reporting);
pthread_mutexattr_settype(&attr_reporting, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutexattr_init(&attr_recursive);
pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE);
/* To emulate OSX behavior, we need to be able both to detect deadlocks
* (so we can log them), and also hang the thread when one occurs.
* the simple way to do that is to set up a locked mutex we can
* force a deadlock on.
*/
pthread_mutex_init(&deadlock, &attr_normal);
pthread_mutex_lock(&deadlock);
baseConditionClass = [NSCondition class];
baseConditionLockClass = [NSConditionLock class];
baseLockClass = [NSLock class];
baseRecursiveLockClass = [NSRecursiveLock class];
tracedConditionClass = [GSTracedCondition class];
tracedConditionLockClass = [GSTracedConditionLock class];
tracedLockClass = [GSTracedLock class];
tracedRecursiveLockClass = [GSTracedRecursiveLock class];
untracedConditionClass = [GSUntracedCondition class];
untracedConditionLockClass = [GSUntracedConditionLock class];
untracedLockClass = [GSUntracedLock class];
untracedRecursiveLockClass = [GSUntracedRecursiveLock class];
}
}
NSRecursiveLock
NSRecursiveLock也是對mutex遞回鎖的封裝,API跟NSLock基本一致
NSCondition
NSCondition是對mutex和cond的封裝
具體使用代碼如下
@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end
@implementation NSConditionDemo
- (instancetype)init
{
if (self = [super init]) {
self.condition = [[NSCondition alloc] init];
self.data = https://www.cnblogs.com/funkyRay/p/[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
{
[self.condition lock];
NSLog(@"__remove - begin");
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"洗掉了元素");
[self.condition unlock];
}
// 執行緒2
// 往陣列中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信號
[self.condition signal];
// 廣播
// [self.condition broadcast];
[self.condition unlock];
}
@end
```##### 分析底層實作`NSCondition`也遵守了`NSLocking`協議,說明其內部已經封裝了鎖的相關代碼
@interface NSCondition : NSObject
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
我們通過`GNUstep Base`也可以看到其初始化方法里對`pthread_mutex_t`進行了封裝
@implementation NSCondition
-
(id) allocWithZone: (NSZone*)z
{
if (self == baseConditionClass && YES == traceLocks)
{
return class_createInstance(tracedConditionClass, 0);
}
return class_createInstance(self, 0);
} -
(void) initialize
{
[NSLock class]; // Ensure mutex attributes are set up.
}
- (id) init
{
if (nil != (self = [super init]))
{
if (0 != pthread_cond_init(&_condition, NULL))
{
DESTROY(self);
}
else if (0 != pthread_mutex_init(&_mutex, &attr_reporting))
{
pthread_cond_destroy(&_condition);
DESTROY(self);
}
}
return self;
}
#### NSConditionLock
`NSConditionLock`是對`NSCondition`的進一步封裝,可以設定具體的條件值
通過設定條件值可以對執行緒做依賴控制執行順序,具體使用見示例代碼
@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
-
(instancetype)init
{
// 創建的時候可以設定一個條件
// 如果不設定,默認就是0
if (self = [super init]) {
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
}
return self;
} -
(void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
} -
(void)__one
{
// 不需要任何條件,只有沒有鎖就加鎖
[self.conditionLock lock];NSLog(@"__one");
sleep(1);[self.conditionLock unlockWithCondition:2];
} -
(void)__two
{
// 根據對應條件來加鎖
[self.conditionLock lockWhenCondition:2];NSLog(@"__two");
sleep(1);[self.conditionLock unlockWithCondition:3];
} -
(void)__three
{
[self.conditionLock lockWhenCondition:3];NSLog(@"__three");
[self.conditionLock unlock];
}
@end
// 列印的先后順序為:1、2、3
#### dispatch\_queue\_t
我們可以直接使用`GCD`的串行佇列,也是可以實作執行緒同步的,具體代碼可以參考`GCD`部分的示例代碼
#### dispatch_semaphore
`semaphore`叫做”信號量”
信號量的初始值,可以用來控制執行緒并發訪問的最大數量
示例代碼如下
@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end
@implementation SemaphoreDemo
-
(instancetype)init
{
if (self = [super init]) {
// 初始化信號量
// 最多只開5條執行緒,也就是可以5條執行緒同時訪問同一塊空間,然后加鎖,其他執行緒再進來就只能等待了
self.semaphore = dispatch_semaphore_create(5);
// 最多只開1條執行緒
self.ticketSemaphore = dispatch_semaphore_create(1);
self.moneySemaphore = dispatch_semaphore_create(1);
}
return self;
} -
(void)__drawMoney
{
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);[super __drawMoney];
dispatch_semaphore_signal(self.moneySemaphore);
} -
(void)__saveMoney
{
dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);[super __saveMoney];
dispatch_semaphore_signal(self.moneySemaphore);
} -
(void)__saleTicket
{
// 如果信號量的值>0就減1,然后往下執行代碼
// 當信號量的值<=0時,當前執行緒就會進入休眠等待(直到信號量的值>0)
dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);[super __saleTicket];
// 讓信號量的值加1
dispatch_semaphore_signal(self.ticketSemaphore);
}
@end
#### @synchronized
`@synchronized`是對`mutex`遞回鎖的封裝
示例代碼如下
@interface SynchronizedDemo: BaseDemo
@end
@implementation SynchronizedDemo
-
(void)__drawMoney
{
// @synchronized需要加鎖的是同一個物件才行
@synchronized([self class]) {
[super __drawMoney];
}
} -
(void)__saveMoney
{
@synchronized([self class]) { // objc_sync_enter
[super __saveMoney];
} // objc_sync_exit
} -
(void)__saleTicket
{
static NSObject *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSObject alloc] init];
});@synchronized(lock) {
[super __saleTicket];
}
}
@end
##### 原始碼分析
我們可以通程序式運行中轉匯編看到,最終都會呼叫`objc_sync_enter`

我們可以通過`objc4`中`objc-sync.mm`來分析對應的原始碼實作
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = https://www.cnblogs.com/funkyRay/p/id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
可以看到會根據傳進來的`obj`找到對應的`SyncData`
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
在找到`SyncData`里面的成員變數`recursive_mutex_t`的真實型別,里面有一個遞回鎖
using recursive_mutex_t = recursive_mutex_tt
class recursive_mutex_tt : nocopy_t {
os_unfair_recursive_lock mLock; // 遞回鎖
public:
constexpr recursive_mutex_tt() : mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT) {
lockdebug_remember_recursive_mutex(this);
}
constexpr recursive_mutex_tt(__unused const fork_unsafe_lock_t unsafe)
: mLock(OS_UNFAIR_RECURSIVE_LOCK_INIT)
{ }
void lock()
{
lockdebug_recursive_mutex_lock(this);
os_unfair_recursive_lock_lock(&mLock);
}
void unlock()
{
lockdebug_recursive_mutex_unlock(this);
os_unfair_recursive_lock_unlock(&mLock);
}
void forceReset()
{
lockdebug_recursive_mutex_unlock(this);
bzero(&mLock, sizeof(mLock));
mLock = os_unfair_recursive_lock OS_UNFAIR_RECURSIVE_LOCK_INIT;
}
bool tryLock()
{
if (os_unfair_recursive_lock_trylock(&mLock)) {
lockdebug_recursive_mutex_lock(this);
return true;
}
return false;
}
bool tryUnlock()
{
if (os_unfair_recursive_lock_tryunlock4objc(&mLock)) {
lockdebug_recursive_mutex_unlock(this);
return true;
}
return false;
}
void assertLocked() {
lockdebug_recursive_mutex_assert_locked(this);
}
void assertUnlocked() {
lockdebug_recursive_mutex_assert_unlocked(this);
}
};
然后我們分析獲取`SyncData`的實作方法`id2data`,通過`obj`從`LIST_FOR_OBJ`真正取出資料
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData *listp = &LIST_FOR_OBJ(object);
SyncData result = NULL;
if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
SyncData *data = https://www.cnblogs.com/funkyRay/p/(SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
item->lockCount++;
break;
case RELEASE:
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = https://www.cnblogs.com/funkyRay/p/*listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
endif
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = https://www.cnblogs.com/funkyRay/p/result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
`LIST_FOR_OBJ`是一個哈希表,哈希表的實作就是將傳進來的`obj`作為`key`,然后對應的鎖為`value`
define LIST_FOR_OBJ(obj) sDataLists[obj].data // 哈希表
static StripedMap
// 哈希表的實作就是將傳進來的物件作為key,然后對應的鎖為value
通過原始碼分析我們也可以看出,`@synchronized`內部的鎖是遞回鎖
### 鎖的比較
#### 性能比較排序
下面是每個鎖按性能從高到低排序
- os\_unfair\_lock- OSSpinLock- dispatch\_semaphore- pthread\_mutex- dispatch\_queue(DISPATCH\_QUEUE\_SERIAL)- NSLock- NSCondition- pthread_mutex(recursive)- NSRecursiveLock- NSConditionLock- @synchronized
選擇性最高的鎖
- dispatch\_semaphore- pthread\_mutex
#### 互斥鎖、自旋鎖的比較
##### 什么情況使用自旋鎖
- 預計執行緒等待鎖的時間很短- 加鎖的代碼(臨界區)經常被呼叫,但競爭情況很少發生- CPU資源不緊張- 多核處理器
##### 什么情況使用互斥鎖
- 預計執行緒等待鎖的時間較長- 單核處理器(盡量減少CPU的消耗)- 臨界區有IO操作(IO操作比較占用CPU資源)- 臨界區代碼復雜或者回圈量大- 臨界區競爭非常激烈
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/274024.html
標籤:iOS
