如今操做系統基本都是多任務操做系統,即同時有大量可調度實體在運行。在多任務操做系統中,同時運行的多個任務可能:ios
同步:是指散步在不一樣任務之間的若干程序片斷,它們的運行必須嚴格按照規定的某種前後次序。最基本的場景就是:多個線程在運行過程當中協同步調,按照預約的前後次序運行。好比A任務的運行依賴於B任務產生的數據。程序員
互斥:是指散步在不一樣任務之間的若干程序片斷,當某個任務運行其中一個程序片斷時,其餘任務就不能運行它們之間的任一程序片斷,直到該任務運行完畢。最基本的場景就是:一個公共資源同一時刻只能被一個進程使用。面試
咱們可使用鎖來解決多線程的同步和互斥問題,基本的鎖包括三類:互斥鎖
自旋鎖
讀寫鎖
, 其餘的好比條件鎖
遞歸鎖
信號量
都是上層的封裝和實現。objective-c
互斥鎖是一種用於多線程編程中,防止兩條線程同時對同一公共資源(比 如全局變量)進行讀寫的機制。該目的經過將代碼切片成一個一個的臨界區而達成。算法
互斥鎖能夠分爲 遞歸鎖(recursive mutex)
和 非遞歸鎖(non-recursive mutex)
。兩者惟一的區別是,同一個線程能夠屢次獲取同一個遞歸鎖,不會產生死鎖。而若是一個線程屢次獲取同一個非遞歸鎖,則會產生死鎖。macos
原子性
:若是一個線程鎖定了一個互斥量,沒有其餘線程在同一時間能夠成功鎖定這個互斥量;惟一性
:若是一個線程鎖定了一個互斥量,在它解除鎖定以前,沒有其餘線程能夠鎖定這個互斥量;非繁忙等待
:若是一個線程鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不佔用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定爲止,第二個線程則被喚醒並繼續執行,同時鎖定這個互斥量。#include <pthread.h>
#include <time.h>
// 初始化一個互斥鎖。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 對互斥鎖上鎖,若互斥鎖已經上鎖,則調用者一直阻塞,直到互斥鎖解鎖後再上鎖。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 調用該函數時,若互斥鎖未加鎖,則上鎖,返回 0;若互斥鎖已加鎖,則函數直接返回失敗,即 EBUSY。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 當線程試圖獲取一個已加鎖的互斥量時,pthread_mutex_timedlock 互斥量
// 容許綁定線程阻塞時間。即非阻塞加鎖互斥量。
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
// 對指定的互斥鎖解鎖。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 銷燬指定的一個互斥鎖。互斥鎖在使用完畢後,必需要對互斥鎖進行銷燬,以釋放資源。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
複製代碼
對於 pthread_mutex 來講,比較重要的是鎖的類型,摘自百度百科:編程
PTHREAD_MUTEX_NORMAL
:不提供死鎖檢測。嘗試從新鎖定互斥鎖會致使死鎖。若是某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或未鎖定,則將產生不肯定的行爲。PTHREAD_MUTEX_ERRORCHECK
: 提供錯誤檢查。若是某個線程嘗試從新鎖定的互斥鎖已經由該線程鎖定,則將返回錯誤。若是某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。PTHREAD_MUTEX_RECURSIVE
:該互斥鎖會保留鎖定計數這一律念。線程首次成功獲取互斥鎖時,鎖定計數會設置爲 1。線程每從新鎖定該互斥鎖一次,鎖定計數就增長 1。線程每解除鎖定該互斥鎖一次,鎖定計數就減少 1。 鎖定計數達到 0 時,該互斥鎖便可供其餘線程獲取。若是某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。PTHREAD_MUTEX_DEFAULT
: 嘗試以遞歸方式鎖定該互斥鎖將產生不肯定的行爲。對於不是由調用線程鎖定的互斥鎖,若是嘗試解除對它的鎖定,則會產生不肯定的行爲。若是嘗試解除鎖定還沒有鎖定的互斥鎖,則會產生不肯定的行爲。一個便捷的建立互斥鎖的方式,它作了其餘互斥鎖所作的全部的事情。swift
@synchronized(object)
指令使用的 object
爲該鎖的惟一標識,只有當標識相同時,才知足互斥。若是你在不一樣的線程中傳過去的是同樣的標識符,先得到鎖的會鎖定代碼塊,另外一個線程將被阻塞,若是傳遞的是不一樣的標識符,則不會形成線程阻塞。數組
- (void)synchronized
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
sleep(2);
NSLog(@"線程1");
}
NSLog(@"線程1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(self) {
NSLog(@"線程2");
}
});
}
打印:
2020-04-26 17:58:14.534038+0800 lock[3891:797979] 線程1
2020-04-26 17:58:14.534250+0800 lock[3891:797979] 線程1解鎖成功
2020-04-26 17:58:14.534255+0800 lock[3891:797981] 線程2
複製代碼
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
複製代碼
將@synchronized(obj)
clang編譯後的僞代碼以下:緩存
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
複製代碼
進入 objc4-756.2
源碼
typedef struct SyncData {
id object;
recursive_mutex_t mutex;
struct SyncData* nextData;
int threadCount;
} SyncData;
typedef struct SyncList {
SyncData *data;
spinlock_t lock;
} SyncList;
// Use multiple parallel lists to decrease contention among unrelated objects.
#define COUNT 16
#define HASH(obj) ((((uintptr_t)(obj)) >> 5) & (COUNT - 1))
#define LOCK_FOR_OBJ(obj) sDataLists[HASH(obj)].lock
#define LIST_FOR_OBJ(obj) sDataLists[HASH(obj)].data
static SyncList sDataLists[COUNT];
複製代碼
SyncData
結構體 :
obj
obj
關聯的 recursive_mutex_t 鎖
。nextData
,因此能夠把每一個 SyncData 結構體看作是鏈表中的一個節點。syncData
對象中的鎖會被一些線程使用或等待,threadCount
就是此時這些線程的數量。syncData結構體
會被緩存,threadCount= 0
表明這個syncData實例能夠被複用.SyncList
結構體:
SyncData
當作是鏈表中的節點,每一個 SyncList
結構體都有個指向 SyncData
節點鏈表頭部的指針,也有一個用於防止多個線程對此列表作併發修改的鎖。sDataLists
結構體數組:
LOCK_FOR_OBJ(obj)
和 LIST_FOR_OBJ(obj)
當調用 objc_sync_enter(obj)
時,它用 obj
內存地址的哈希值查找合適的 SyncData
,而後將其上鎖。
當調用 objc_sync_exit(obj)
時,它查找合適的 SyncData
並將其解鎖。
// Begin synchronizing on 'obj'.
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = 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;
}
BREAKPOINT_FUNCTION(
void objc_sync_nil(void)
);
複製代碼
obj
分配一個 遞歸鎖
並存儲在哈希表中obj
經過 id2data(obj, ACQUIRE)
封裝成 SyncData(obj)
- (void)synchronizedTest
{
self.testArray = [NSMutableArray array];
for (NSInteger i = 0; i < 200000; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
_testArray = [NSMutableArray array];
});
}
}
複製代碼
_testArray
在不一樣的線程中不斷的 retain
release
,會存在某個時刻,多個線程同時對_testArray
進行release
,致使crash。@synchronizing
鎖住 _testArray
,還會crash麼?- (void)synchronizedTest
{
self.testArray = [NSMutableArray array];
for (NSInteger i = 0; i < 200000; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (_testArray) {
_testArray = [NSMutableArray array];
}
});
}
}
複製代碼
被鎖對象爲nil時,@synchronized並不盡如人意,怎麼才能解決問題呢?使用NSLock。
{
self.testArray = [NSMutableArray array];
NSLock *lock = [[NSLock alloc] init];
for (NSInteger i = 0; i < 200000; i ++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lock];
_testArray = [NSMutableArray array];
[lock unlock];
});
}
}
複製代碼
NSLock 底層pthread_mutex_lock
實現的, 屬性爲 PTHREAD_MUTEX_ERRORCHECK
。遵循 NSLocking 協議。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
複製代碼
lock
:加鎖unlock
:解鎖tryLock
:嘗試加鎖,若是失敗的話返回 NOlockBeforeDate
: 在指定Date以前嘗試加鎖,若是在指定時間以前都不能加鎖,則返回NO- (void)nslock
{
NSLock *lock = [[NSLock alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程1");
sleep(2);
[lock unlock];
NSLog(@"線程1解鎖成功");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保證讓線程2的代碼後執行
[lock lock];
NSLog(@"線程2");
[lock unlock];
});
}
打印:
2020-04-26 20:27:36.474376+0800 lock[6554:889229] 線程1
2020-04-26 20:27:38.474856+0800 lock[6554:889229] 線程1解鎖成功
2020-04-26 20:27:38.474880+0800 lock[6554:889230] 線程2
複製代碼
pthread_mutex_lock
實現的,屬性爲 PTHREAD_MUTEX_RECURSIVE
。NSRecursiveLock
和 NSLock
的區別在於:NSRecursiveLock 能夠在 同一個線程
中重複加鎖,NSRecursiveLock 會記錄上鎖和解鎖的次數,當兩者平衡的時候,纔會釋放鎖,其它線程才能夠上鎖成功。@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
複製代碼
- (void)recursiveLock
{
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"current value = %d", value);
testMethod(value - 1);
}
[lock unlock];
};
testMethod(10);
});
}
打印:
2020-04-26 21:40:24.390756+0800 lock[6691:924076] current value = 10
2020-04-26 21:40:24.390875+0800 lock[6691:924076] current value = 9
2020-04-26 21:40:24.390956+0800 lock[6691:924076] current value = 8
2020-04-26 21:40:24.391043+0800 lock[6691:924076] current value = 7
2020-04-26 21:40:24.391131+0800 lock[6691:924076] current value = 6
2020-04-26 21:40:24.391211+0800 lock[6691:924076] current value = 5
2020-04-26 21:40:24.391295+0800 lock[6691:924076] current value = 4
2020-04-26 21:40:24.391394+0800 lock[6691:924076] current value = 3
2020-04-26 21:40:24.391477+0800 lock[6691:924076] current value = 2
2020-04-26 21:40:24.391561+0800 lock[6691:924076] current value = 1
複製代碼
對於 @synchronized
NSLock
NSRecursiveLock
應用場景的我的拙見,若是有問題請各位大佬指正:
NSLock
便可NSRecursiveLock
@synchronized
(本質是對遞歸鎖的封裝,但可以防止一些死鎖, 使用時注意被鎖對象不能爲nil)例以下面的代碼,在 for循環 中不斷建立線程,在各自的線程中又不斷 遞歸 ,這種多線程+遞歸
的狀況下,使用@synchronized
加鎖。
- (void)test
{
for (int i= 0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void (^testMethod)(int);
testMethod = ^(int value){
@synchronized (self) {
if (value > 0) {
NSLog(@"current value = %d",value);
testMethod(value - 1);
}
}
};
testMethod(10);
});
}
}
複製代碼
線程反覆檢查鎖變量是否可用。因爲線程在這一過程當中保持執行, 所以是一種忙等。一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖。 自旋鎖避免了進程上下文的調度開銷,所以對於線程只會阻塞很短期的場合是有效的。
自旋鎖的API和互斥鎖類似,把 pthread_mutex_xxx()
中 mutex
換成 spin
,如:pthread_spin_init()
。
自旋鎖目前已不安全,可能會出現優先級翻轉問題。假設有三個準備執行的任務A、B、C 和 須要互斥訪問的共享資源S,三個任務的優先級依次是 A > B > C;
說到自旋鎖,不得不提屬性修飾符 atomic。
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) {
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset) {
reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
複製代碼
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
// 直接替換
oldValue = *slot;
*slot = newValue;
} else {
// 加鎖替換
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
複製代碼
newValue
替換 oldValue
spinlock_t
類型的鎖,並給鎖加鹽。在鎖環境下 newValue
替換 oldValue
。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);
}
複製代碼
spinlock_t
看名字很像自旋鎖,可是自旋鎖已經不安全了。來看下 spinlock_t
的定義
using spinlock_t = mutex_tt<DEBUG>;
using mutex_locker_t = mutex_tt<LOCKDEBUG>::locker;
複製代碼
看來,蘋果在底層使用 mutex_locker_t
替換了 spinlock_t
。mutex_locker_t
又是什麼?
/*!
* @typedef os_unfair_lock
*
* @abstract
* Low-level lock that allows waiters to block efficiently on contention.
*
* In general, higher level synchronization primitives such as those provided by
* the pthread or dispatch subsystems should be preferred.
*
* The values stored in the lock should be considered opaque and implementation
* defined, they contain thread ownership information that the system may use
* to attempt to resolve priority inversions.
*
* This lock must be unlocked from the same thread that locked it, attemps to
* unlock from a different thread will cause an assertion aborting the process.
*
* This lock must not be accessed from multiple processes or threads via shared
* or multiply-mapped memory, the lock implementation relies on the address of
* the lock value and owning process.
*
* Must be initialized with OS_UNFAIR_LOCK_INIT
*
* @discussion
* Replacement for the deprecated OSSpinLock. Does not spin on contention but
* waits in the kernel to be woken up by an unlock.
*
* As with OSSpinLock there is no attempt at fairness or lock ordering, e.g. an
* unlocker can potentially immediately reacquire the lock before a woken up
* waiter gets an opportunity to attempt to acquire the lock. This may be
* advantageous for performance reasons, but also makes starvation of waiters a
* possibility.
*/
OS_UNFAIR_LOCK_AVAILABILITY
typedef struct os_unfair_lock_s {
uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;
複製代碼
仍是要讚歎下蘋果官方註釋,太詳細了。
OS_UNFAIR_LOCK_INIT
下初始化。鎖裏面包含線程全部權信息,用來解決優先級反轉問題
同一線程
解除鎖定,嘗試從其餘線程解除鎖定將致使斷言停止進程。多個進程或線程
訪問此鎖,鎖的實現依賴於鎖值和所屬進程的地址。OSSpinLock
(iOS 10廢棄)。atomic 會對屬性的 setter方法 、getter方法 分別加鎖,生成了原子性的 setter、getter。這裏的原子性也就意味着:假設當前有兩個線程,線程A執行 getter 方法的時候,線程B若是想要執行 setter 方法,必需要等到getter方法執行完畢以後才能執行。
簡而言之,atomic只能保證代碼進入 getter 或者 setter 函數內部是安全的,一旦出現了同時getter 和 setter,多線程只能靠程序員本身保證。因此atomic屬性和使用@property的多線程安全沒有直接的聯繫。
舉個例子:線程A 和 線程B 都對屬性 num 執行10000次 + 1 操做。若是線程安全的話,程序運行結束後,num的值應該是20000。
@property (atomic, assign) NSInteger num;
- (void)atomicTest {
//Thread A
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.num = self.num + 1;
NSLog(@"%@ -- %ld", [NSThread currentThread], (long)self.num);
}
});
//Thread B
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10000; i ++) {
self.num = self.num + 1;
NSLog(@"%@ -- %ld", [NSThread currentThread], (long)self.num);
}
});
}
打印:
···
2020-04-28 16:35:55.126996+0800 lock[10384:1662304] <NSThread: 0x600000ea3c00>{number = 3, name = (null)} -- 19994
2020-04-28 16:35:55.127083+0800 lock[10384:1662299] <NSThread: 0x600000eecdc0>{number = 5, name = (null)} -- 19995
2020-04-28 16:35:55.127165+0800 lock[10384:1662304] <NSThread: 0x600000ea3c00>{number = 3, name = (null)} -- 19996
2020-04-28 16:35:55.127250+0800 lock[10384:1662299] <NSThread: 0x600000eecdc0>{number = 5, name = (null)} -- 19997
2020-04-28 16:35:55.127341+0800 lock[10384:1662304] <NSThread: 0x600000ea3c00>{number = 3, name = (null)} -- 19998
複製代碼
self.num = self.num + 1
方法:
另外,atomic因爲要鎖住該屬性,所以它會消耗更多的資源,性能會很低,要比 nonatomic 慢20倍。因此iOS移動端開發,咱們通常使用nonatomic。可是在mac開發中,atomic就有意義了。
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分紅讀者和寫者,讀者只對共享資源進行讀訪問,寫者則須要對共享資源進行寫操做。這種鎖相對於自旋鎖而言,能提升併發性,由於在多處理器系統中,它容許同時有多個讀者來訪問共享資源,最大可能的讀者數爲實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
讀模式下加鎖狀態
、寫模式加鎖狀態
、不加鎖狀態
。多讀單寫
#include <pthread.h>
// 初始化讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
// 申請讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
// 申請寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
// 嘗試以非阻塞的方式來在讀寫鎖上獲取寫鎖。若是有任何的讀者或寫者持有該鎖,則當即失敗返回。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
// 解鎖
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
// 銷燬讀寫鎖
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
複製代碼
// 用於讀寫的併發隊列:
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 用戶數據中心, 可能多個線程須要數據訪問:
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;
- (void)readWriteTest
{
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
self.dataCenterDic = [NSMutableDictionary dictionary];
dispatch_queue_t queue = dispatch_queue_create("com.akironer", DISPATCH_QUEUE_CONCURRENT);
// 模擬多線程狀況下寫
for (NSInteger i = 0; i < 5; i ++) {
dispatch_async(queue, ^{
[self ak_setObject:[NSString stringWithFormat:@"akironer--%ld", (long)i] forKey:@"Key"];
});
}
// 模擬多線程狀況下讀
for (NSInteger i = 0; i < 20; i ++) {
dispatch_async(queue, ^{
[self ak_objectForKey:@"Key"];
});
}
// 模擬多線程狀況下寫
for (NSInteger i = 0; i < 10; i ++) {
dispatch_async(queue, ^{
[self ak_setObject:[NSString stringWithFormat:@"iOS--%ld", (long)i] forKey:@"Key"];
});
}
}
#pragma mark - 讀數據
- (id)ak_objectForKey:(NSString *)key {
__block id obj;
// 同步讀取數據:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
NSLog(@"讀:%@--%@", obj, [NSThread currentThread]);
sleep(1);
});
return obj;
}
#pragma mark - 寫數據
- (void)ak_setObject:(id)obj forKey:(NSString *)key {
// 異步柵欄調用設置數據: 屏蔽同步
dispatch_barrier_async(self.concurrent_queue, ^{
[self.dataCenterDic setObject:obj forKey:key];
NSLog(@"寫:%@--%@", obj, [NSThread currentThread]);
sleep(1);
});
}
複製代碼
與互斥鎖不一樣,條件鎖是用來等待而不是用來上鎖的。條件鎖用來自動阻塞一個線程,直 到某特殊狀況發生爲止。一般條件鎖和互斥鎖通常同時使用。
條件鎖是利用線程間共享的全局變量進行同步 的一種機制,使咱們能夠睡眠等待某種條件出現,主要包括兩個動做:
NSCondition
的底層經過 pthread_cond_t
實現的。NSCondition 的對象實際上做爲一個鎖和一個線程檢查器NSCondition
實現了 NSLocking協議
,當多個線程訪問同一段代碼時,會以 wait
爲分水嶺。一個線程等待另外一個線程 unlock
以後,再走 wait
以後的代碼。@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
複製代碼
lock
: 通常用於多線程同時訪問、修改同一個數據源,保證在同一 時間內數據源只被訪問、修改一次,其餘線程的命令須要在lock 外等待,只到 unlock ,纔可訪問unlock
: 解鎖wait
:讓當前線程處於等待狀態signal
:任意通知一個線程broadcast
:通知全部等待的線程- (void)testConditon
{
self.testCondition = [[NSCondition alloc] init];
//建立生產-消費者
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; // 生產
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; // 消費
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self consumer]; // 消費
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self producer]; // 生產
});
}
}
- (void)producer
{
[self.testCondition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產一個 現有 count %zd",self.ticketCount);
[self.testCondition signal];
[self.testCondition unlock];
}
- (void)consumer
{
// 線程安全
[self.testCondition lock];
while (self.ticketCount == 0) {
NSLog(@"等待 count %zd",self.ticketCount);
// 保證正常流程
[self.testCondition wait];
}
//注意消費行爲,要在等待條件判斷以後
self.ticketCount -= 1;
NSLog(@"消費一個 還剩 count %zd ",self.ticketCount);
[self.testCondition unlock];
}
打印:
2020-04-27 17:46:43.232762+0800 lock[7444:1140032] 生產一個 現有 count 1
2020-04-27 17:46:43.232900+0800 lock[7444:1140032] 生產一個 現有 count 2
2020-04-27 17:46:43.233001+0800 lock[7444:1140032] 消費一個 還剩 count 1
2020-04-27 17:46:43.233109+0800 lock[7444:1140066] 消費一個 還剩 count 0
2020-04-27 17:46:43.233209+0800 lock[7444:1140070] 等待 count 0
2020-04-27 17:46:43.233308+0800 lock[7444:1140030] 等待 count 0
2020-04-27 17:46:43.233406+0800 lock[7444:1140057] 等待 count 0
2020-04-27 17:46:43.233508+0800 lock[7444:1140058] 生產一個 現有 count 1
2020-04-27 17:46:43.233611+0800 lock[7444:1140070] 消費一個 還剩 count 0
2020-04-27 17:46:43.233713+0800 lock[7444:1140059] 等待 count 0
2020-04-27 17:46:43.234100+0800 lock[7444:1140061] 生產一個 現有 count 1
2020-04-27 17:46:43.234343+0800 lock[7444:1140030] 消費一個 還剩 count 0
複製代碼
NSConditionLock
藉助 NSCondition
來實現,它的本質就是一個生產者-消費者模型。NSConditionLock 的內部持有一個 NSCondition 對象,以及 _condition_value 屬性,在初始化時就會對這個屬性進行賦值.
NSConditionLock
實現了 NSLocking協議
,一個線程會等待另外一個線程 unlock
或者 unlockWithCondition:
以後再走 lock
或者 lockWhenCondition:
以後的代碼。
相比於 NSCondition
, NSConditonLock
自帶一個條件探測變量,使用更加靈活。
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
@end
複製代碼
lock
: 表示 xxx 期待得到鎖,若是沒有其餘線程得到鎖(不須要判斷內部的 condition條件) 那它能執行此行如下代碼,若是已經有其餘線程得到鎖(多是條件鎖,或者無條件 鎖),則等待,直至其餘線程解鎖condition
:內部condition條件。這屬性很是重要,外部condition條件
與 內部condition條件
相同纔會獲取到 lock 對象;反之阻塞當前線程,直到condition相同lockWhenCondition:(NSInteger)conditionA
:表示在沒有其餘線程得到該鎖的前提下,該鎖 內部condition條件 不等於 條件A,不能得到鎖,仍然等待。若是鎖 內部condition 等於A條件,則進入代碼區,同時設置它得到該鎖,其餘任何線程都將等待它代碼 的完成,直至它解鎖。unlockWithCondition:(NSInteger)conditionA
: 表示釋放鎖,同時把 內部condition條件 設置爲A條件return = lockWhenCondition:(NSInteger)conditionA beforeDate:(NSDate *)limitA
:表示若是被鎖定(沒得到 鎖),並超過 時間A 則再也不阻塞線程。可是注意: 返回的值是NO, 它沒有改變鎖的狀態,這個函數的目的在於能夠實現兩種狀態下的處理- (void)testConditonLock
{
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[conditionLock lockWhenCondition:2];
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
}
打印:
2020-04-27 18:00:21.876356+0800 lock[7484:1148383] 線程 3
2020-04-27 18:00:21.876629+0800 lock[7484:1148384] 線程 2
2020-04-27 18:00:21.876751+0800 lock[7484:1148386] 線程 1
複製代碼
[NSConditionLock lockWhenCondition:1]
,此時由於不知足當前條件,所 以會進入 waiting 狀態,當前進入到 waiting 時,會釋放當前的互斥鎖。[NSConditionLock lock]
,本質上是調用 [NSConditionLock lockBeforeDate:],這裏不須要比對條件值,因此線程 3 會打印[NSConditionLock lockWhenCondition:2]
,由於知足條件值,因此線程 2 會打印,打印完成後會調用 [NSConditionLock unlockWithCondition:1]
將 value 設置爲 1,併發送 boradcast[NSConditionLock lockWhenCondition:]
會根據傳入的 condition 值和 Value 值進 行對比,若是不相等,這裏就會阻塞,進入線程池,不然的話就繼續代碼執行[NSConditionLock unlockWithCondition:]
會先更改當前的 value 值,而後進行廣 播,喚醒當前的線程。信號量普遍用於進程或線程間的同步和互斥,信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。
#include <semaphore.h>
// 初始化信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 信號量 P 操做(減 1)
int sem_wait(sem_t *sem);
// 以非阻塞的方式來對信號量進行減 1 操做
int sem_trywait(sem_t *sem);
// 信號量 V 操做(加 1)
int sem_post(sem_t *sem);
// 獲取信號量的值
int sem_getvalue(sem_t *sem, int *sval);
// 銷燬信號量
int sem_destroy(sem_t *sem);
複製代碼
GCD 的 dispatch_semaphore,能夠參考iOS進階之路 (十六)多線程 - GCD
在 ibireme 大神的 再也不安全的 OSSpinLock中,對各類鎖的性能作了測試(加鎖後當即解鎖,並無計算競爭時候的時間消耗)
OSSpinLock
性能最高,但它已經再也不安全。@synchronized
的效率最低,相信學習了本篇文章,@synchronized 再也不是加鎖的首先。