@synchronized
NSLock
、pthread_mutex
NSCondition
NSConditionLock
NSRecursiveLock
pthread_mutex(recursive)
dispatch_semaphore
在探索源碼以前先寫一個票的demo,先看沒有加鎖的狀況下運行是怎樣的swift
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.ticketCount = 20;
[self lg_testSaleTicket];
}
- (void)lg_testSaleTicket{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket{
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當前餘票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當前車票已售罄");
}
}
複製代碼
運行結果: 緩存
@synchronized
從上面的實例代碼中能夠看到多線程訪問同一個數據的時候會出現問題,可能同時一個多個線程訪問一個數據,此時爲了不這種問題能夠加鎖同時只讓一個線程訪問數據,具體用法以下:再看運行結果:
發現測試就沒有上述問題了。
再看源碼實現 首先開啓彙編調試 發現底層會調用兩個方法:
objc_sync_exit
和objc_sync_enter
,也能夠經過clang查看編譯後的文件驗證此時在下個符號斷點
objc_sync_exit
和objc_sync_enter
發現源碼在
libobjc.A.dylib
庫中,而後再去庫裏面找源碼
先看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;
複製代碼
發現是一個單鏈表結構markdown
nextData
指向下一個SyncDataobject
一個對象指針,對象是objc_object即OC對象,不難猜想,它保存了被鎖定對象obj的指針threadCount
記錄正在使用這個代碼塊的線程數mutex
遞歸鎖、獲取到該結構體對象後,就是調用它的lock()方法再看id2data
源碼 具體源碼註釋圖中都有解釋。
總結一下大體流程:數據結構
lockCount+1
返回就好,objc_sync_exit
方法對應的是減一lockCount+1
返回,objc_sync_exit
方法對應的是減一threadCount
加1而且存到緩存中,若是也沒有其餘線程使用則threadCount
置爲1存到緩存中緩存結構圖: 多線程
OSSpinLock
OSSpinLock
被棄用,其替代方案是內部封裝了os_unfair_lock
,而os_unfair_lock
在加鎖時會處於休眠狀態,而不是自旋鎖的忙等狀態併發
atomic適用於OC中屬性的修飾符,其自帶一把自旋鎖,屬性在調用setter
和getter
方法的時候會加一把鎖框架
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
id *slot = (id*) ((char*)self + offset);
...
if (!atomic) {//未加鎖
oldValue = *slot;
*slot = newValue;
} else {//加鎖
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
...
}
複製代碼
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();//加鎖
id value = objc_retain(*slot);
slotlock.unlock();//解鎖
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
複製代碼
從源碼中能夠看出,對於atomic修飾的屬性,進行了spinlock_t加鎖處理,可是在前文中提到OSSpinLock已經廢棄了,這裏的spinlock_t在底層是經過os_unfair_lock替代了OSSpinLock實現的加鎖async
pthread_mutex
pthread_mutex
就是互斥鎖,當鎖被佔用,其餘線程申請鎖時,不會一直忙等待,而是阻塞線程並睡眠
使用示例:函數
// 導入頭文件
#import <pthread.h>
// 全局聲明互斥鎖
pthread_mutex_t _lock;
// 初始化互斥鎖
pthread_mutex_init(&_lock, NULL);
// 加鎖
pthread_mutex_lock(&_lock);
// 這裏作須要線程安全操做
// 解鎖
pthread_mutex_unlock(&_lock);
// 釋放鎖
pthread_mutex_destroy(&_lock);
複製代碼
NSLock
首先通關斷點調試查看NSLock
源碼的位置以下圖: 此時發現
NSLock
的源碼在Foundation
框架中,由於OC
的Foundation
框架是閉源的因此看不了源碼,可是swift
的Foundation
框架是開源的,因此咱們也已查看swift
的Foundation
框架,由於也就是語法不同大致實現邏輯都差很少 能夠發現
NSLock
底層就是對pthread_mutex
的封裝,應爲NSLock
是一把互斥鎖,會阻塞線程等待任務執行,因此使用NSLock
須要注意不能重入NSLock
鎖,會形成線程相互等待的狀況,形成死鎖
NSRecursiveLock
是互斥鎖中的遞歸鎖,可被同一線程屢次獲取的鎖,而不會產生死鎖。什麼意思呢,一個線程已經得到了鎖,開始執行受鎖保護的代碼(鎖還未釋放),若是這段代碼調用了其餘函數,而被調用的函數又要獲取這個鎖,此時已然能夠得到鎖並正常執行,而不會死鎖。底層也是對pthread_mutex
的封裝底層實現代碼也和NSLock
很想lock
方法和unLock
方法都和NSLock
是同樣的無非就是init
的時候NSRecursiveLock
設置了該鎖的類型是個遞歸鎖 使用示例:
NSCondition
NSCondition
也是一把互斥鎖他和NSLock
的區別在於
NSLock
在獲取不到鎖的時候自動使線程進入休眠,鎖被釋放後線程又自動被喚醒
NSCondition
可使咱們更加靈活的控制線程狀態,在任何須要的時候使線程進入休眠或喚醒它
例如一個生產消費的例子,只有生產出來了商品才能被消費者售賣,消費者再買東西的時候商品沒了就要等待生產者產出後在進行購買,示例代碼以下:
- (void)td_testConditon{
_testCondition = [[NSCondition alloc] init];
//建立生產-消費者
for (int i = 0; i < 50; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self td_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self td_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self td_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self td_producer];
});
}
}
- (void)td_producer{
[_testCondition lock]; // 操做的多線程影響
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產一個 現有 count %zd",self.ticketCount);
[_testCondition signal]; // 信號
[_testCondition unlock];
}
- (void)td_consumer{
[_testCondition lock]; // 操做的多線程影響
if (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
[_testCondition wait];
}
//注意消費行爲,要在等待條件判斷以後
self.ticketCount -= 1;
NSLog(@"消費一個 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
複製代碼
底層和
NSLock
很像都是對pthread_mutex_t
的封裝,無非就是使用了pthread_cond_t
的條件
NSConditionLock
條件鎖,通俗的將就是有條件的互斥鎖
使用NSConditionLock對象,能夠確保線程僅在知足特定條件時才能獲取鎖。 一旦得到了鎖並執行了代碼的關鍵部分,線程就能夠放棄該鎖並將關聯條件設置爲新的條件。 條件自己是任意的:您能夠根據應用程序的須要定義它們。
#pragma mark -- NSConditionLock
- (void)td_testConditonLock{
// 信號量
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1]; // conditoion = 1 內部 Condition 匹配
// -[NSConditionLock lockWhenCondition: beforeDate:]
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
sleep(0.1);
NSLog(@"線程 2");
// self.myLock.value = 1;
[conditionLock unlockWithCondition:1]; // _value = 2 -> 1
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
複製代碼
NSConditionLock
的源碼其實就是NSCondition
和NSLock
結合封裝的一把鎖