iOS探索 细数iOS中的那些锁
2020/5/24 23:26:32
本文主要是介绍iOS探索 细数iOS中的那些锁,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
欢迎阅读iOS探索系列(按序阅读食用效果更加)
- iOS探索 alloc流程
- iOS探索 内存对齐&malloc源码
- iOS探索 isa初始化&指向分析
- iOS探索 类的结构分析
- iOS探索 cache_t分析
- iOS探索 方法的本质和方法查找流程
- iOS探索 动态方法解析和消息转发机制
- iOS探索 浅尝辄止dyld加载流程
- iOS探索 类的加载过程
- iOS探索 分类、类拓展的加载过程
- iOS探索 isa面试题分析
- iOS探索 runtime面试题分析
- iOS探索 KVC原理及自定义
- iOS探索 KVO原理及自定义
- iOS探索 多线程原理
- iOS探索 多线程之GCD应用
- iOS探索 多线程之GCD底层分析
- iOS探索 多线程之NSOperation
- iOS探索 多线程面试题分析
- iOS探索 细数iOS中的那些锁
写在前面
多线程在日常开发中能起到性能优化的作用,但是一旦没用好就会造成线程不安全,本文就来讲讲如何保证线程安全
一、锁
1.线程安全
当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果
即线程内操作了一个线程外的非线程安全变量,这个时候一定要考虑线程安全和同步
2.检测安全
3.锁的作用
锁
作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire
)锁,并在访问结束之后释放(Release
)锁。如果锁已经被占用,其它试图获取锁的线程会等待,直到锁重新可用
注:不要将过多的其他操作代码放到锁里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了
4.锁的分类
在iOS中锁的基本种类只有两种:互斥锁
、自旋锁
,其他的比如条件锁
、递归锁
、信号量
都是上层的封装和实现
而在JAVA中锁占有更大份额,有兴趣可以去研究一下
5. 互斥锁
互斥锁
(Mutual exclusion,缩写Mutex
)防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
互斥锁
又分为:
递归锁
:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用非递归锁
:不可重入,必须等锁释放后才能再次获取锁
6. 自旋锁
自旋锁
:线程反复检查锁变量是否可⽤。由于线程在这⼀过程中保持执⾏,
因此是⼀种忙等待
。⼀旦获取了⾃旋锁,线程会⼀直保持该锁,直⾄显式释
放⾃旋锁
⾃旋锁
避免了进程上下⽂的调度开销,因此对于线程只会阻塞很短时间的场合是有效的
7.互斥锁和自旋锁的区别
互斥锁
在线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时线程会被唤醒自旋锁
的线程则会一直处于等待状态(忙等待)不会进入休眠——因此效率高
接下来就一一来介绍iOS中用到的各种锁
二、自旋锁
1.OSSpinLock
自从OSSpinLock
出现了安全问题之后就废弃了。自旋锁之所以不安全,是因为自旋锁由于获取锁时,线程会一直处于忙等待状态,造成了任务的优先级反转
而OSSpinLock
忙等的机制就可能造成高优先级一直running等待
,占用CPU时间片;而低优先级任务无法抢占时间片,变成迟迟完不成,不释放锁的情况
2.atomic
2.1 atomic原理
在iOS探索 KVC原理及自定义中有提到自动生成的setter方法会根据修饰符不同调用不同方法,最后统一调用reallySetProperty
方法,其中就有一段关于atomic
修饰词的代码
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); } 复制代码
比对一下atomic
的逻辑分支:
- 原子性修饰的属性进行了
spinlock
加锁处理 - 非原子性的属性除了没加锁,其他逻辑与
atomic
一般无二
等等,前面不是刚说OSSpinLock
因为安全问题被废弃了吗,但是苹果源码怎么还在使用呢?其实点进去就会发现用os_unfair_lock
替代了OSSpinLock
(iOS10之后替换)
using spinlock_t = mutex_tt<LOCKDEBUG>; class mutex_tt : nocopy_t { os_unfair_lock mLock; ... } 复制代码
同时为了哈希不冲突,还使用
加盐操作
进行加锁
getter
方法亦是如此:atomic修饰的属性进行加锁处理
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); } 复制代码
2.2 atomic修饰的属性绝对安全吗?
atomic
只能保证setter、getter方法的线程安全,并不能保证数据安全
atomic
修饰的index变量
分别在两次并发异步for循环10000次
后输出的结果并不等于20000
。由此可以得出结论:
atomic
保证变量在取值和赋值时的线程安全- 但不能保证
self.index+1
也是安全的 - 如果改成
self.index=i
是能保证setter方法的线程安全的
3. 读写锁
读写锁
实际是一种特殊的自旋锁
,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁
而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的CPU
数
- 写者是排他性的,⼀个读写锁同时只能有⼀个写者或多个读者(与CPU数相关),但不能同时既有读者⼜有写者。在读写锁保持期间也是抢占失效的
- 如果读写锁当前没有读者,也没有写者,那么写者可以⽴刻获得读写锁,否则它必须⾃旋在那⾥,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以⽴即获得该读写锁,否则读者必须⾃旋在那⾥,直到写者释放该读写锁
// 导入头文件 #import <pthread.h> // 全局声明读写锁 pthread_rwlock_t lock; // 初始化读写锁 pthread_rwlock_init(&lock, NULL); // 读操作-加锁 pthread_rwlock_rdlock(&lock); // 读操作-尝试加锁 pthread_rwlock_tryrdlock(&lock); // 写操作-加锁 pthread_rwlock_wrlock(&lock); // 写操作-尝试加锁 pthread_rwlock_trywrlock(&lock); // 解锁 pthread_rwlock_unlock(&lock); // 释放锁 pthread_rwlock_destroy(&lock); 复制代码
平时很少会直接使用读写锁pthread_rwlock_t
,更多的是采用其他方式,例如使用栅栏函数完成读写锁的需求
三、互斥锁
1.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); 复制代码
YYKit的YYMemoryCach有使用到pthread_mutex
2.@synchronized
@synchronized
可能是日常开发中用的比较多的一种互斥锁,因为它的使用比较简单,但并不是在任意场景下都能使用@synchronized
,且它的性能较低
@synchronized (obj) {} 复制代码
接下来就通过源码探索来看一下@synchronized
在使用中的注意事项
- 通过汇编能发现
@synchronized
就是实现了objc_sync_enter
和objc_sync_exit
两个方法 - 通过符号断点能知道这两个方法都是在
objc源码
中的 - 通过clang也能得到一些信息:
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); } 复制代码
2.1 源码分析
在objc源码
中找到objc_sync_enter
和objc_sync_exit
// 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; } // End synchronizing on 'obj'. // Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR int objc_sync_exit(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, RELEASE); if (!data) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } else { bool okay = data->mutex.tryUnlock(); if (!okay) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; } } } else { // @synchronized(nil) does nothing } return result; } 复制代码
- 首先从它的注释中
recursive mutex
可以得出@synchronized
是递归锁 - 如果锁的对象
obj
不存在时分别会走objc_sync_nil()
和不做任何操作
(源码分析可以先解决简单的逻辑分支)
BREAKPOINT_FUNCTION( void objc_sync_nil(void) ); 复制代码
这也是@synchronized
作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁
- 正常情况下(obj存在)会通过
id2data
方法生成一个SyncData
对象
nextData
指的是链表中下一个SyncDataobject
指的是当前加锁的对象threadCount
表示使用该对象进行加锁的线程数mutex
即对象所关联的锁
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; 复制代码
2.2 准备SyncData
static SyncData* id2data(id object, enum usage why) { spinlock_t *lockp = &LOCK_FOR_OBJ(object); SyncData **listp = &LIST_FOR_OBJ(object); SyncData* result = NULL; ... } 复制代码
id2data
先将返回对象SyncData类型的result
准备好,后续进行数据填充
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock #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) { } }; 复制代码
其中通过两个宏定义去取得SyncList
中的data
和lock
——static StripedMap<SyncList> sDataLists
可以理解成 NSArray<id> list
既然@synchronized
能在任意地方(VC、View、Model等)使用,那么底层必然维护着一张全局的表(类似于weak表)。而从SyncList
和SyncData
的结构可以证实系统确实在底层维护着一张哈希表,里面存储着SyncList结构
的数据。SyncList
和SyncData
的关系如下图所示:
2.3 使用快速缓存
static SyncData* id2data(id object, enum usage why) { ... #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) { // 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 ... } 复制代码
这里有个重要的知识点——TLS
:TLS
全称为Thread Local Storage
,在iOS中每个线程都拥有自己的TLS
,负责保存本线程的一些变量, 且TLS
无需锁保护
快速缓存
的含义为:定义两个变量SYNC_DATA_DIRECT_KEY/SYNC_COUNT_DIRECT_KEY
,与tsl_get_direct/tls_set_direct
配合可以从线程局部缓存中快速取得SyncCacheItem.data
和SyncCacheItem.lockCount
如果在缓存中找到当前对象,就拿出当前被锁的次数lockCount
,再根据传入参数类型(获取、释放、查看)对lockCount
分别进行操作
- 获取资源
ACQUIRE
:lockCount++
并根据key
值存入被锁次数 - 释放资源
RELEASE
:lockCount++
并根据key
值存入被锁次数。如果次数变为0,此时锁也不复存在,需要从快速缓存移除并清空线程数threadCount
- 查看资源
check
:不操作
lockCount表示被锁的次数,意味着能多次进入,从侧面表现出了递归性
2.4 获取该线程下的SyncCache
这个逻辑分支是找不到确切的线程标记只能进行所有的缓存遍历
static SyncData* id2data(id object, enum usage why) { ... 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; } } ... } 复制代码
这里介绍一下SyncCache
和SyncCacheItem
typedef struct { SyncData *data; //该缓存条目对应的SyncData unsigned int lockCount; //该对象在该线程中被加锁的次数 } SyncCacheItem; typedef struct SyncCache { unsigned int allocated; //该缓存此时对应的缓存大小 unsigned int used; //该缓存此时对应的已使用缓存大小 SyncCacheItem list[0]; //SyncCacheItem数组 } SyncCache; 复制代码
SyncCacheItem
用来记录某个SyncData
在某个线程中被加锁的记录,一个SyncData
可以被多个SyncCacheItem
持有SyncCache
用来记录某个线程中所有SyncCacheItem
,并且记录了缓存大小以及已使用缓存大小
2.5 全局哈希表查找
快速、慢速流程都没找到缓存就会来到这步——在系统保存的哈希表进行链式查找
static SyncData* id2data(id object, enum usage why) { ... 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; } } ... } 复制代码
lockp->lock()
并不是在底层对锁进行了封装,而是在查找过程前后进行了加锁操作for循环
遍历链表,如果有符合的就goto done
- 寻找链表中未使用的
SyncData
并作标记
- 寻找链表中未使用的
- 如果是
RELEASE
或CHECK
直接goto done
- 如果第二步中有发现第一次使用的的对象就将
threadCount
标记为1且goto done
2.6 生成新数据并写入缓存
static SyncData* id2data(id object, enum usage why) { ... 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); 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 = result; cache->list[cache->used].lockCount = 1; cache->used++; } } ... } 复制代码
- 第三步情况均不满足(即链表不存在——对象对于全部线程来说是第一次加锁)就会创建
SyncData
并存在result
里,方便下次进行存储 - done分析:
- 先将前面的lock锁解开
- 如果是
RELEASE
类型直接返回nil - 对
ACQUIRE
类型和对象的断言判断 !fastCacheOccupied
分支表示支持快速缓存,将该SyncCacheItem
数据写入快速缓存中- 否则将该
SyncCacheItem
存入该线程对应的SyncCache
中
2.7 疑难解答
- 不能使用
非OC对象
作为加锁条件——id2data
中接收参数为id类型 - 多次锁同一个对象会有什么后果吗——会从高速缓存中拿到data,所以只会锁一次对象
- 都说@synchronized性能低——是因为在底层
增删改查
消耗了大量性能 - 加锁对象不能为nil,否则加锁无效,不能保证线程安全
- (void)test { _testArray = [NSMutableArray array]; for (int i = 0; i < 200000; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ @synchronized (self.testArray) { self.testArray = [NSMutableArray array]; } }); } } 复制代码
上面代码一运行就会崩溃,原因是因为在某一瞬间testArray
释放了为nil,但哈希表中存的对象也变成了nil,导致synchronized
无效化
解决方案:
- 对
self
进行同步锁,这个似乎太臃肿了 - 使用
NSLock
3.NSLock
3.1 使用
NSLock
是对互斥锁
的简单封装,使用如下:
- (void)test { self.testArray = [NSMutableArray array]; NSLock *lock = [[NSLock alloc] init]; for (int i = 0; i < 200000; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [lock lock]; self.testArray = [NSMutableArray array]; [lock unlock]; }); } } 复制代码
NSLock
在AFNetworking的AFURLSessionManager.m中有使用到
想要了解一下NSLock
的底层原理,但发现其是在未开源的Foundation
源码下面的,但但是Swift对Foundation
却开源了,可以在swift-corelibs-foundation下载到源码来一探究竟
3.2 注意事项
使用互斥锁NSLock
异步并发调用block块,block块内部递归调用自己,问打印什么?
- (void)test { NSLock *lock = [[NSLock alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^block)(int); block = ^(int value) { NSLog(@"加锁前"); [lock lock]; NSLog(@"加锁后"); if (value > 0) { NSLog(@"value——%d", value); block(value - 1); } [lock unlock]; }; block(10); }); } 复制代码
输出结果并没有按代码表面的想法去走,而是只打印了一次value值
加锁前 加锁前 value——10 加锁前 复制代码
原因: 互斥锁在递归调用时会造成堵塞,并非死锁——这里的问题是后面的代码无法执行下去
- 第一次加完锁之后还没出锁就进行递归调用
- 第二次加锁就堵塞了线程(因为不会查询缓存)
解决方案: 使用递归锁NSRecursiveLock
替换NSLock
4.NSRecursiveLock
4.1 使用
NSRecursiveLock
使用和NSLock
类似,如下代码就能解决上个问题
- (void)test { NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^block)(int); block = ^(int value) { [lock lock]; if (value > 0) { NSLog(@"value——%d", value); block(value - 1); } [lock unlock]; }; block(10); }); } 复制代码
NSRecursiveLock
在YYKit中YYWebImageOperation.m中有用到
4.2 注意事项
递归锁在使用时需要注意死锁问题——前后代码相互等待便会产生死锁
上述代码在外层加个for循环
,问输出结果?
- (void)test { NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; for (int i = 0; i < 10; i++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void (^block)(int); block = ^(int value) { [lock lock]; if (value > 0) { NSLog(@"value——%d", value); block(value - 1); } [lock unlock]; }; block(10); }); } } 复制代码
运行代码会崩溃,并会提示野指针
错误
即 线程1中加锁1、同时线程2中加锁2-> 解锁1等待解锁2 -> 解锁2等待解锁1 -> 无法结束解锁——形成死锁
解决: 可以采用使用缓存的@synchronized
,因为它对对象进行锁操作,会先从缓存查找是否有锁syncData
存在。如果有,直接返回而不加锁,保证锁的唯一性
5.dispatch_semaphore
在GCD应用篇章已经对信号量进行过讲解
6.NSCondition
NSCondition
是一个条件锁,可能平时用的不多,但与信号量相似:线程1需要等到条件1满足才会往下走,否则就会堵塞等待,直至条件满足
同样的能在Swift源码
中找到关于NSCondition
部分
open class NSCondition: NSObject, NSLocking { internal var mutex = _MutexPointer.allocate(capacity: 1) internal var cond = _ConditionVariablePointer.allocate(capacity: 1) public override init() { pthread_mutex_init(mutex, nil) pthread_cond_init(cond, nil) } deinit { pthread_mutex_destroy(mutex) pthread_cond_destroy(cond) } open func lock() { pthread_mutex_lock(mutex) } open func unlock() { pthread_mutex_unlock(mutex) } open func wait() { pthread_cond_wait(cond, mutex) } open func wait(until limit: Date) -> Bool { guard var timeout = timeSpecFrom(date: limit) else { return false } return pthread_cond_timedwait(cond, mutex, &timeout) == 0 } open func signal() { pthread_cond_signal(cond) } open func broadcast() { pthread_cond_broadcast(cond) // wait signal } open var name: String? } 复制代码
从上述精简后的代码可以得出以下几点:
NSCondition
是对mutex
和cond
的一种封装(cond
就是用于访问和操作特定类型数据的指针)wait
操作会阻塞线程,使其进入休眠状态,直至超时signal
操作是唤醒一个正在休眠等待的线程broadcast
会唤醒所有正在等待的线程
7.NSConditionLock
顾名思义,就是NSCondition
+ Lock
那么和NSCondition
的区别在于哪里呢?接下来看一下NSConditionLock
源码
open class NSConditionLock : NSObject, NSLocking { internal var _cond = NSCondition() internal var _value: Int internal var _thread: _swift_CFThreadRef? public convenience override init() { self.init(condition: 0) } public init(condition: Int) { _value = condition } open func lock() { let _ = lock(before: Date.distantFuture) } open func unlock() { _cond.lock() _thread = nil _cond.broadcast() _cond.unlock() } open var condition: Int { return _value } open func lock(whenCondition condition: Int) { let _ = lock(whenCondition: condition, before: Date.distantFuture) } open func `try`() -> Bool { return lock(before: Date.distantPast) } open func tryLock(whenCondition condition: Int) -> Bool { return lock(whenCondition: condition, before: Date.distantPast) } open func unlock(withCondition condition: Int) { _cond.lock() _thread = nil _value = condition _cond.broadcast() _cond.unlock() } open func lock(before limit: Date) -> Bool { _cond.lock() while _thread != nil { if !_cond.wait(until: limit) { _cond.unlock() return false } } _thread = pthread_self() _cond.unlock() return true } open func lock(whenCondition condition: Int, before limit: Date) -> Bool { _cond.lock() while _thread != nil || _value != condition { if !_cond.wait(until: limit) { _cond.unlock() return false } } _thread = pthread_self() _cond.unlock() return true } open var name: String? } 复制代码
从上述代码可以得出以下几点:
NSConditionLock
是NSCondition
加线程数的封装NSConditionLock
可以设置锁条件,而NSCondition
只是无脑的通知信号
8.os_unfair_lock
由于OSSpinLock
自旋锁的bug,替代方案是内部封装了os_unfair_lock
,而os_unfair_lock
在加锁时会处于休眠状态,而不是自旋锁的忙等状态
9.互斥锁性能对比
四、总结
OSSpinLock
不再安全,底层用os_unfair_lock
替代atomic
只能保证setter、getter时线程安全,所以更多的使用nonatomic
来修饰读写锁
更多使用栅栏函数来实现@synchronized
在底层维护了一个哈希链表进行data
的存储,使用recursive_mutex_t
进行加锁NSLock
、NSRecursiveLock
、NSCondition
和NSConditionLock
底层都是对pthread_mutex
的封装NSCondition
和NSConditionLock
是条件锁,当满足某一个条件时才能进行操作,和信号量dispatch_semaphore
类似- 普通场景下涉及到线程安全,可以用
NSLock
- 循环调用时用
NSRecursiveLock
- 循环调用且有线程影响时,请注意死锁,如果有死锁问题请使用
@synchronized
写在后面
日常开发中若需要使用线程锁来保证线程安全,请多考虑一下再选择使用哪个锁,@synchronized
并不是最优的选择。作为一名优秀的开发不但能让App正常运行,更要让它优质地运行、优化它的性能
参考资料
synchronized实现原理及缺陷分析
iOS底层学习 - 多线程之中的锁
iOS开发中的11种锁以及性能对比
这篇关于iOS探索 细数iOS中的那些锁的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值