多線程的安全離不開鎖的使用瓷叫,常見鎖的性能:
一、鎖的分類
基本的鎖就包括了2大類:自旋鎖 互斥鎖
.
其他的比如條件鎖
楞陷、遞歸鎖
怔鳖、信號量
都是上層的封裝實(shí)現(xiàn).
-
讀寫鎖
-多
(線程)讀單
(線程)寫
實(shí)際是一種特殊的自旋鎖
,它把對共享資源的訪問者劃分成讀者和寫者猜谚,讀者只對共享資源進(jìn)行讀訪問败砂,寫者則需要對共享資源進(jìn)行寫操作。讀寫鎖相對于自旋鎖而言魏铅,能提高并發(fā)性
昌犹,因?yàn)樵?code>多處理器系統(tǒng)中,它允許同時(shí)有多個(gè)讀者來訪問共享資源览芳,最大可能的讀者數(shù)為實(shí)際的邏輯 CPU 數(shù)斜姥。寫者是排他性的,一個(gè)讀寫鎖同時(shí)只能有一個(gè)寫者或多個(gè)讀者
(與CPU數(shù)相關(guān)),但不能同時(shí)既有讀者又有寫者
铸敏。在讀寫鎖保持期間也是搶占失效的缚忧。- 如果讀寫鎖當(dāng)前沒有讀者,也沒有寫者杈笔,那么寫者可以立刻獲得讀寫鎖闪水,否則
寫者必須自旋在那里
,直到?jīng)]有任何寫者或讀者蒙具; - 如果讀寫鎖沒有寫者球榆,那么讀者可以立即獲得該讀寫鎖,否則
讀者必須自旋在那里
禁筏,直到寫者釋放該讀寫鎖持钉。
- 如果讀寫鎖當(dāng)前沒有讀者,也沒有寫者杈笔,那么寫者可以立刻獲得讀寫鎖闪水,否則
1、自旋鎖
- 線程反復(fù)檢查鎖變量是否可用篱昔。由于線程在這一過程中保持執(zhí)行每强, 因此是一種忙等待;
- 一旦獲取了自旋鎖州刽,線程會一直保持該鎖空执,直至顯式釋放自旋鎖;
- 自旋鎖避免了進(jìn)程上下文的調(diào)度開銷怀伦,因此對于線程只會阻塞很短時(shí)間的場合是有效的脆烟,小而精 的任務(wù);
- 因 一直檢查詢問鎖是否打開可用房待,
耗費(fèi)性能比較高
邢羔。
2、互斥鎖
- 是一種多線程編程中桑孩,防止兩條線程同時(shí)對同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制拜鹤。它通過將代碼切片成一個(gè)一個(gè)的臨界區(qū) 而實(shí)現(xiàn)。
- 保證同一時(shí)間只有一條線程可進(jìn)行某執(zhí)行任務(wù) - 類似保證了同步的功能流椒。
當(dāng)發(fā)現(xiàn)別的線程正在操作任務(wù)敏簿,當(dāng)前線程獲取互斥鎖失敗,當(dāng)前線程進(jìn)入休眠 (就緒狀態(tài)
-等待被調(diào)度執(zhí)行
) --> 一直等到其他線程打開鎖之后 -->喚起 執(zhí)行
宣虾。 - 常見的互斥鎖 - 互斥鎖分為遞歸和非遞歸鎖
3.1NSLock
3.2@synchronized
3.3pthread_mutex
2.1) 遞歸鎖
- 就是同一個(gè)線程 可以加鎖 N 次而不會引發(fā)死鎖惯裕。
- 常見的遞歸鎖
2.1NSRecursiveLock
2.2pthread_mutex(recursive)
3、條件鎖
- 即
條件變量
绣硝。當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠蜻势,也就
是鎖住了。當(dāng)資源被分配到了鹉胖,條件鎖打開握玛,進(jìn)程繼續(xù)運(yùn)行够傍。 - 常見的條件鎖:
2.1NSCondition
2.2NSConditionLock
4、信號量 semaphore - dispatch_semaphore
- 信號量是一種更高級的
同步機(jī)制
挠铲,互斥鎖可以說是
semaphore
在僅取值0/1時(shí)的特例冕屯。 - 信號量可以有更多的取值空間,用來實(shí)現(xiàn)更加復(fù)雜的同步拂苹,而不單單是線程間互斥安聘。
二、鎖的原理分析
以售票舉例瓢棒,示例代碼如下:
- (void)my_lockDemo {
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(@"當(dāng)前余票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當(dāng)前車票已售罄");
}
}
運(yùn)行程序搞挣,輸出結(jié)果如下:
/**
當(dāng)前余票還剩:17張
當(dāng)前余票還剩:18張
當(dāng)前余票還剩:16張
當(dāng)前余票還剩:18張
當(dāng)前余票還剩:13張
當(dāng)前余票還剩:13張
當(dāng)前余票還剩:13張
當(dāng)前余票還剩:12張
... 更多打印不必貼全 ...
*/
由上示例,余票票數(shù)是有問題的音羞,對于多線程操作,數(shù)據(jù)的安全性必須考慮仓犬。
1嗅绰、@synchronized
原理
給上面的示例代碼添加@synchronized
鎖如下挣跋,再次運(yùn)行工程:
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當(dāng)前余票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當(dāng)前車票已售罄");
}
}
/** 打印輸出結(jié)果如下:
當(dāng)前余票還剩:19張
當(dāng)前余票還剩:18張
當(dāng)前余票還剩:17張
當(dāng)前余票還剩:16張
當(dāng)前余票還剩:15張
當(dāng)前余票還剩:14張
當(dāng)前余票還剩:13張
當(dāng)前余票還剩:12張
... 更多打印不必貼全 ...
*/
由上基公,余票的數(shù)據(jù)是正確的吼畏,線程安全已解決悄窃。@synchronized
是如何實(shí)現(xiàn)線程安全的呢檀轨?
- 將
main.m
文件編譯成cpp
:
clang -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
/目標(biāo)文件路徑/main.m
-o main3.cpp
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
/**************** 編譯后 .cpp --> @synchronized ******************/
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
{ // 代碼塊區(qū)域
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
} _sync_exit(_sync_obj);
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
由上文件可看到花鹅,@synchronized
主要的2行代碼objc_sync_enter
和 objc_sync_exit
着撩。
- 運(yùn)行工程夜矗,打開debug匯編調(diào)試也可找到
objc_sync_enter / objc_sync_exit
点骑,如下:
0x104e8851e <+46>: callq 0x104e8c754 ; symbol stub for: objc_sync_enter
... more info ...
0x104e885f1 <+257>: callq 0x104e8c75a ; symbol stub for: objc_sync_exit
通過匯編酣难,跳進(jìn)到objc_sync_enter
,找到其所在庫:libobjc.A.dylib
1.1黑滴、@synchronized
源碼分析
打開 libobjc 源碼工程憨募,全局搜索:
// 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;
}
1.1.1、@synchronized
遞歸互斥鎖
SyncData
: --> 鏈表結(jié)構(gòu)
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;
recursive_mutex_t
--> recursive_mutex_tt
:
template <bool Debug>
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(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);
}
};
由上源碼可驗(yàn)證@synchronized
是個(gè)遞歸互斥鎖袁辈。
1.1.2菜谣、@synchronized
的實(shí)現(xiàn)
id2data()
:
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
// 1、快速查找
// 支持線程 key晚缩,通過set get 存取
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
// 檢查每個(gè)線程 單條目快速緩存 是否匹配對象
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {// 當(dāng)前的 object 和data中的一致
// 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++;// lockCount鎖的次數(shù) --> 可重復(fù)被鎖
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
// 2尾膊、緩存查找 - 遍歷哈希list
// Check per-thread cache of already-owned locks for matching object
// 檢查 已擁有鎖 的每個(gè)線程緩存 是否匹配對象
SyncCache *cache = fetch_cache(NO);
/**
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
SyncCacheItem list[0];
} SyncCache;
*/
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
// 從 緩存 list 中 remove
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.
// 保證這塊的線程安全 lock
lockp->lock();
// 3、對象所對應(yīng)的鏈表 遍歷查找
{
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;
}
// 第一次進(jìn)來
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.
// 沒有荞彼,創(chuàng)建新鏈表插入哈希list中
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 = *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);// KVC
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);// lockCount
} else
#endif
{
// Save in thread cache 保存到哈希list
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
由SyncData **listp = &LIST_FOR_OBJ(object);
-->LIST_FOR_OBJ()
:
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;
//
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
// StripedMap:
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
// 哈希
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
... more info ...
}
SyncList
結(jié)構(gòu):
-
@synchronized
邏輯流程:
對象objc
的@synchronized
:- 首先在棧存空間查找冈敛,匹配上:
lockCount++
并返回; - 沒有繼續(xù)在全局的哈希list緩存查找卿泽,匹配到:
lockCount++
并返回莺债; - 沒有找到,則開始遍歷當(dāng)前對象
objc
所對應(yīng)的鏈表遍歷查滋觉,找到,goto done
齐邦,保存到緩存椎侠; - 沒找到,創(chuàng)建結(jié)點(diǎn)給新鏈表措拇,
4.1 若支持線程key
-SUPPORT_DIRECT_THREAD_KEYS
,通過tls_set_direct(k,value)
- 以KVC
方式保存到tls
(tls:本地局部的線程緩存)我纪;
tls
: 線程局部存儲(Thread Local Storage,TLS)
:是操作系統(tǒng)為線
程單獨(dú)提供的私有空間,通常只有有限的容量丐吓。
4.2 不支持浅悉,將新鏈表保存到緩存,即 開辟空間將其存到哈希list中券犁。
- 首先在棧存空間查找冈敛,匹配上:
*
問題:
@synchronized
性能差原因由其實(shí)現(xiàn)原理也可知术健,鏈表的查找速度很慢,盡管做了緩存粘衬,但其速度仍是相較慢的荞估。
為何@synchronized
性能那么低還要用它呢?
--> 使用方便稚新,封裝性高不用關(guān)心內(nèi)部加解鎖勘伺。
*
注意點(diǎn):
@synchronized()
使用時(shí),需要鎖住的對象要注意其生命周期褂删,一般常見的是鎖self
飞醉,其原因是對象的生命是和所在的self
,并非都用self
屯阀。
--> 我們在使用@synchronized()
時(shí)考慮保證要鎖住的對象其生命正常即可缅帘。
示例代碼如下:
- (void)my_lock {
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
for (int i=0; i<2000; i++) {
// 1. 不加鎖 --> objc_release 野指針,對象多次釋放 --> crash
// dispatch_async(dispatch_get_global_queue(0, 0), ^{
// _muArray = [NSMutableArray array];
// });
// 2. @synchronized (self) --> 正常
// dispatch_async(dispatch_get_global_queue(0, 0), ^{
// @synchronized (self) {
// _muArray = [NSMutableArray array];
// }
// });
// 2.1 @synchronized (_muArray) --> crash
// dispatch_async(dispatch_get_global_queue(0, 0), ^{
// @synchronized (_muArray) {// 它可能在某時(shí)刻為nil,@synchronized鎖對nil不進(jìn)行任何操作so鎖不住
// _muArray = [NSMutableArray array];
// }
// });
// 3. 信號量 --> 正常
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
_muArray = [NSMutableArray array];
dispatch_semaphore_signal(sem);
}
}
2蹲盘、NSLock
2.1股毫、NSLock
源碼
NSLock
在Foundation
框架中,其為開源召衔,但swift
的Foundation
開源铃诬,我們這里以swift
的Foundation
源碼進(jìn)行探究。
open class NSLock: NSObject, NSLocking {
internal var mutex = _MutexPointer.allocate(capacity: 1)
#if os(macOS) || os(iOS) || os(Windows)
private var timeoutCond = _ConditionVariablePointer.allocate(capacity: 1)
private var timeoutMutex = _MutexPointer.allocate(capacity: 1)
#endif
public override init() {
#if os(Windows)
InitializeSRWLock(mutex)
InitializeConditionVariable(timeoutCond)
InitializeSRWLock(timeoutMutex)
#else
pthread_mutex_init(mutex, nil)
#if os(macOS) || os(iOS)
// lock 的創(chuàng)建必須 init苍凛,下面代碼可說明原因:
// 條件數(shù)cond 和互斥mutex 的 init 操作
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
}
// ... ... more code ... ...
open func lock() {
#if os(Windows)
AcquireSRWLockExclusive(mutex)
#else
pthread_mutex_lock(mutex)// 互斥鎖 lock
#endif
}
open func unlock() {
#if os(Windows)
ReleaseSRWLockExclusive(mutex)
AcquireSRWLockExclusive(timeoutMutex)
WakeAllConditionVariable(timeoutCond)
ReleaseSRWLockExclusive(timeoutMutex)
#else
pthread_mutex_unlock(mutex)
#if os(macOS) || os(iOS)
// Wakeup any threads waiting in lock(before:)
pthread_mutex_lock(timeoutMutex) // 解鎖
pthread_cond_broadcast(timeoutCond) // 廣播
pthread_mutex_unlock(timeoutMutex) // 解鎖
#endif
#endif
}
// ... ... more code ... ...
}
NSLock
鎖的lock
和 unlock
是通過pthead
進(jìn)行了一層封裝了趣席,NSLock
鎖的性能次于pthread_mutex
鎖一點(diǎn)點(diǎn)。
* Tip
: NSLock
- NSRecursiveLock
- @synchronized
三者使用場景
示例代碼:
- (void)my_NSLock {
NSLock *lock = [[NSLock alloc]init];// 互斥鎖
NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];// 遞歸鎖
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
for (int i =0; i<100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
static void(^testMethod)(int);
// [lock lock];// 1 - crash
[recursiveLock lock];
testMethod = ^(int value) {
// 使用 @synchronized 簡單方便
// @synchronized (self) {
// if (value > 0) {
// NSLog(@"當(dāng)前 value = %d",value);
// testMethod(value - 1);
// }
// };
// [lock lock];// 2 - 堵死
if (value > 0) {
NSLog(@"當(dāng)前 value = %d",value);
testMethod(value - 1);
}
// [lock unlock];// 1 - crash
// [lock unlock];// 2 - 堵死
[recursiveLock unlock];
};
// [lock lock];// 2 - 正常 - 但它造成的堵塞情況太嚴(yán)重
testMethod(10);
// [lock unlock];// 2 - 正常
});
}
}
NSRecursiveLock
遞歸鎖和NSLock
同是基于pthread_mutex
封裝醇蝴,2者不同處主要在init
設(shè)置:
NSRecursiveLock
的init
類型設(shè)置標(biāo)記為 pthread_mutex_recursive
:
public override init() {
super.init()
#if os(Windows)
InitializeCriticalSection(mutex)
InitializeConditionVariable(timeoutCond)
InitializeSRWLock(timeoutMutex)
#else
#if CYGWIN
var attrib : pthread_mutexattr_t? = nil
#else
var attrib = pthread_mutexattr_t()
#endif
withUnsafeMutablePointer(to: &attrib) { attrs in
pthread_mutexattr_init(attrs)
// 類型設(shè)置標(biāo)記為遞歸鎖: pthread_mutex_recursive
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(mutex, attrs)
}
#if os(macOS) || os(iOS)
pthread_cond_init(timeoutCond, nil)
pthread_mutex_init(timeoutMutex, nil)
#endif
#endif
}
遞歸鎖的使用宣肚,在循環(huán)嵌套場景中比較適用,而NSRecursiveLock
遞歸鎖和@synchronized
鎖類似悠栓,使用上個(gè)人更傾向于@synchronized
霉涨,適用便利性更強(qiáng)按价。
3、NSCondition
和 NSConditionLock
條件鎖
-
NSCondition
對象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器:-->生產(chǎn)消費(fèi)者模型
- 鎖的目的主要是 在檢測條件時(shí)保護(hù)數(shù)據(jù)源笙瑟,執(zhí)行 條件引發(fā)的任務(wù);
- 線程檢查器主要是 根據(jù)條件決定是否繼續(xù)運(yùn)行線程楼镐,即線程是否被阻塞。
-
NSConditionLock
鎖往枷,一旦一個(gè)線程獲得鎖框产,其他線程一定等待,它的使用可以攜帶著條件错洁。
3.1秉宿、NSCondition
使用
示例代碼:
#pragma mark - NSCondition -
- (void)my_testConditon {
_testCondition = [[NSCondition alloc] init];
// 創(chuàng)建生產(chǎn)-消費(fèi)者
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self my_producer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self my_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self my_consumer];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self my_producer];
});
}
}
- (void)my_producer {
[_testCondition lock];
self.ticketCount = self.ticketCount + 1;
NSLog(@"生產(chǎn)一個(gè) 現(xiàn)有 count %zd",self.ticketCount);
[_testCondition signal];
[_testCondition unlock];
}
- (void)my_consumer {
// 線程安全
[_testCondition lock];
// while (self.ticketCount == 0) {
// NSLog(@"等待 count %zd",self.ticketCount);
// }
if (self.ticketCount == 0) {// 這里 if 是截不住條件的
NSLog(@"等待 count %zd",self.ticketCount);
// 保證正常流程
[_testCondition wait];
}
//注意消費(fèi)行為,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費(fèi)一個(gè) 還剩 count %zd ",self.ticketCount);
[_testCondition unlock];
}
3.2屯碴、NSConditionLock
條件鎖的使用
示例代碼:
- (void)my_testConditonLock {
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// 1 和 創(chuàng)建的 conditionLock 條件值對比 - 1!=2描睦,不執(zhí)行
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// 2 和 創(chuàng)建的 conditionLock 條件值對比 - 2=2,執(zhí)行
[conditionLock lockWhenCondition:2];
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
// 這里 condition 條件值設(shè)為了 1导而,會觸發(fā)通知到在等待的線程1可以執(zhí)行
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
// 執(zhí)行結(jié)果:先線程2后線程1酌摇,3和2執(zhí)行順序不定
}
* Tip
: 原子鎖 - atomic
// 原子鎖模擬代碼
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
@synchronized (self) {
// 加鎖,保證同一時(shí)間只有一條線程可進(jìn)行寫入
_name = name;
}
}
-
atomic
原子鎖特點(diǎn)
1.默認(rèn)的屬性嗡载;
2.只在屬性的setter
方法中添加了自旋鎖 spin
,保證在同一時(shí)間仍稀,只有一條線程可進(jìn)行寫操作 --> 單寫多讀. setter 分析
3.一定程度保證科線程安全洼滚,但耗費(fèi)大量資源。 -
nonatomic
1.非原子屬性技潘,沒有鎖遥巴,非線程安全;
2.性能高于atomic
享幽;
建議
:
1. iOS開發(fā)中多用nonatomic
铲掐。因?yàn)?code>atomic耗費(fèi)性能有點(diǎn)高,大概是nonatomic
的至少10倍
值桩,客戶端壓力太大摆霉。
2. 盡量避免多線程搶奪同一塊資源。若要搶奪資源并保證線程安全奔坟,可在相應(yīng)位置單獨(dú)加鎖携栋。