多線程的安全離不開鎖的使用,常見鎖的性能:
一、鎖的分類
基本的鎖就包括了2大類:自旋鎖 互斥鎖
.
其他的比如條件鎖
、遞歸鎖
、信號量
都是上層的封裝實現.
-
讀寫鎖
-多
(線程)讀單
(線程)寫
實際是一種特殊的自旋鎖
,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。讀寫鎖相對于自旋鎖而言,能提高并發性
,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源
,最大可能的讀者數為實際的邏輯 CPU 數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者
(與CPU數相關),但不能同時既有讀者又有寫者
。在讀寫鎖保持期間也是搶占失效的。- 如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則
寫者必須自旋在那里
,直到沒有任何寫者或讀者; - 如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則
讀者必須自旋在那里
,直到寫者釋放該讀寫鎖。
- 如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則
1、自旋鎖
- 線程反復檢查鎖變量是否可用。由于線程在這一過程中保持執行, 因此是一種忙等待;
- 一旦獲取了自旋鎖,線程會一直保持該鎖,直至顯式釋放自旋鎖;
- 自旋鎖避免了進程上下文的調度開銷,因此對于線程只會阻塞很短時間的場合是有效的,小而精 的任務;
- 因 一直檢查詢問鎖是否打開可用,
耗費性能比較高
。
2、互斥鎖
- 是一種多線程編程中,防止兩條線程同時對同一公共資源(比如全局變量)進行讀寫的機制。它通過將代碼切片成一個一個的臨界區 而實現。
- 保證同一時間只有一條線程可進行某執行任務 - 類似保證了同步的功能。
當發現別的線程正在操作任務,當前線程獲取互斥鎖失敗,當前線程進入休眠 (就緒狀態
-等待被調度執行
) --> 一直等到其他線程打開鎖之后 -->喚起 執行
。 - 常見的互斥鎖 - 互斥鎖分為遞歸和非遞歸鎖
3.1NSLock
3.2@synchronized
3.3pthread_mutex
2.1) 遞歸鎖
- 就是同一個線程 可以加鎖 N 次而不會引發死鎖。
- 常見的遞歸鎖
2.1NSRecursiveLock
2.2pthread_mutex(recursive)
3、條件鎖
- 即
條件變量
。當進程的某些資源要求不滿足時就進入休眠,也就
是鎖住了。當資源被分配到了,條件鎖打開,進程繼續運行。 - 常見的條件鎖:
2.1NSCondition
2.2NSConditionLock
4、信號量 semaphore - dispatch_semaphore
- 信號量是一種更高級的
同步機制
,互斥鎖可以說是
semaphore
在僅取值0/1時的特例。 - 信號量可以有更多的取值空間,用來實現更加復雜的同步,而不單單是線程間互斥。
二、鎖的原理分析
以售票舉例,示例代碼如下:
- (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(@"當前余票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當前車票已售罄");
}
}
運行程序,輸出結果如下:
/**
當前余票還剩:17張
當前余票還剩:18張
當前余票還剩:16張
當前余票還剩:18張
當前余票還剩:13張
當前余票還剩:13張
當前余票還剩:13張
當前余票還剩:12張
... 更多打印不必貼全 ...
*/
由上示例,余票票數是有問題的,對于多線程操作,數據的安全性必須考慮。
1、@synchronized
原理
給上面的示例代碼添加@synchronized
鎖如下,再次運行工程:
@synchronized (self) {
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"當前余票還剩:%ld張",self.ticketCount);
}else{
NSLog(@"當前車票已售罄");
}
}
/** 打印輸出結果如下:
當前余票還剩:19張
當前余票還剩:18張
當前余票還剩:17張
當前余票還剩:16張
當前余票還剩:15張
當前余票還剩:14張
當前余票還剩:13張
當前余票還剩:12張
... 更多打印不必貼全 ...
*/
由上,余票的數據是正確的,線程安全已解決。@synchronized
是如何實現線程安全的呢?
- 將
main.m
文件編譯成cpp
:
clang -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
/目標文件路徑/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")));
{ // 代碼塊區域
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
。
- 運行工程,打開debug匯編調試也可找到
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
通過匯編,跳進到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
: --> 鏈表結構
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);
}
};
由上源碼可驗證@synchronized
是個遞歸互斥鎖。
1.1.2、@synchronized
的實現
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
// 檢查每個線程 單條目快速緩存 是否匹配對象
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {// 當前的 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鎖的次數 --> 可重復被鎖
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
// 檢查 已擁有鎖 的每個線程緩存 是否匹配對象
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、對象所對應的鏈表 遍歷查找
{
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.
// 沒有,創建新鏈表插入哈希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
結構:
-
@synchronized
邏輯流程:
對象objc
的@synchronized
:- 首先在棧存空間查找,匹配上:
lockCount++
并返回; - 沒有繼續在全局的哈希list緩存查找,匹配到:
lockCount++
并返回; - 沒有找到,則開始遍歷當前對象
objc
所對應的鏈表遍歷查,找到,goto done
,保存到緩存; - 沒找到,創建結點給新鏈表,
4.1 若支持線程key
-SUPPORT_DIRECT_THREAD_KEYS
,通過tls_set_direct(k,value)
- 以KVC
方式保存到tls
(tls:本地局部的線程緩存);
tls
: 線程局部存儲(Thread Local Storage,TLS)
:是操作系統為線
程單獨提供的私有空間,通常只有有限的容量。
4.2 不支持,將新鏈表保存到緩存,即 開辟空間將其存到哈希list中。
- 首先在棧存空間查找,匹配上:
*
問題:
@synchronized
性能差原因由其實現原理也可知,鏈表的查找速度很慢,盡管做了緩存,但其速度仍是相較慢的。
為何@synchronized
性能那么低還要用它呢?
--> 使用方便,封裝性高不用關心內部加解鎖。
*
注意點:
@synchronized()
使用時,需要鎖住的對象要注意其生命周期,一般常見的是鎖self
,其原因是對象的生命是和所在的self
,并非都用self
。
--> 我們在使用@synchronized()
時考慮保證要鎖住的對象其生命正常即可。
示例代碼如下:
- (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) {// 它可能在某時刻為nil,@synchronized鎖對nil不進行任何操作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
源碼進行探究。
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 的創建必須 init,下面代碼可說明原因:
// 條件數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
進行了一層封裝了,NSLock
鎖的性能次于pthread_mutex
鎖一點點。
* 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(@"當前 value = %d",value);
// testMethod(value - 1);
// }
// };
// [lock lock];// 2 - 堵死
if (value > 0) {
NSLog(@"當前 value = %d",value);
testMethod(value - 1);
}
// [lock unlock];// 1 - crash
// [lock unlock];// 2 - 堵死
[recursiveLock unlock];
};
// [lock lock];// 2 - 正常 - 但它造成的堵塞情況太嚴重
testMethod(10);
// [lock unlock];// 2 - 正常
});
}
}
NSRecursiveLock
遞歸鎖和NSLock
同是基于pthread_mutex
封裝,2者不同處主要在init
設置:
NSRecursiveLock
的init
類型設置標記為 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)
// 類型設置標記為遞歸鎖: 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
}
遞歸鎖的使用,在循環嵌套場景中比較適用,而NSRecursiveLock
遞歸鎖和@synchronized
鎖類似,使用上個人更傾向于@synchronized
,適用便利性更強。
3、NSCondition
和 NSConditionLock
條件鎖
-
NSCondition
對象實際上作為一個鎖和一個線程檢查器:-->生產消費者模型
- 鎖的目的主要是 在檢測條件時保護數據源,執行 條件引發的任務;
- 線程檢查器主要是 根據條件決定是否繼續運行線程,即線程是否被阻塞。
-
NSConditionLock
鎖,一旦一個線程獲得鎖,其他線程一定等待,它的使用可以攜帶著條件。
3.1、NSCondition
使用
示例代碼:
#pragma mark - NSCondition -
- (void)my_testConditon {
_testCondition = [[NSCondition alloc] init];
// 創建生產-消費者
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(@"生產一個 現有 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];
}
//注意消費行為,要在等待條件判斷之后
self.ticketCount -= 1;
NSLog(@"消費一個 還剩 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 和 創建的 conditionLock 條件值對比 - 1!=2,不執行
[conditionLock lockWhenCondition:1];
NSLog(@"線程 1");
[conditionLock unlockWithCondition:0];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// 2 和 創建的 conditionLock 條件值對比 - 2=2,執行
[conditionLock lockWhenCondition:2];
NSLog(@"線程 2");
[conditionLock unlockWithCondition:1];
// 這里 condition 條件值設為了 1,會觸發通知到在等待的線程1可以執行
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[conditionLock lock];
NSLog(@"線程 3");
[conditionLock unlock];
});
// 執行結果:先線程2后線程1,3和2執行順序不定
}
* Tip
: 原子鎖 - atomic
// 原子鎖模擬代碼
- (NSString *)name {
return _name;
}
- (void)setName:(NSString *)name {
@synchronized (self) {
// 加鎖,保證同一時間只有一條線程可進行寫入
_name = name;
}
}
-
atomic
原子鎖特點
1.默認的屬性;
2.只在屬性的setter
方法中添加了自旋鎖 spin
,保證在同一時間,只有一條線程可進行寫操作 --> 單寫多讀. setter 分析
3.一定程度保證科線程安全,但耗費大量資源。 -
nonatomic
1.非原子屬性,沒有鎖,非線程安全;
2.性能高于atomic
;
建議
:
1. iOS開發中多用nonatomic
。因為atomic
耗費性能有點高,大概是nonatomic
的至少10倍
,客戶端壓力太大。
2. 盡量避免多線程搶奪同一塊資源。若要搶奪資源并保證線程安全,可在相應位置單獨加鎖。