锁
锁
iOS中锁只分为两大类:
-
自旋锁(spin) :自旋锁其实是while轮询,避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
-
互斥锁(mutex) :等待时进入睡眠或就绪,需要调度上下文。
递归
(recursive): 递归只是一个特性,并不是指锁。其意是可以重复上锁,同一个线程可以加锁N次而不会引发死锁
。
死锁
:系统发现此线程既需要等待又需要执行,不知道怎么执行,所以抛出了_crash异常。
atomic
atomic只是对属性的getter/setter方法进行了加锁操作,这种安全仅仅是get/set的读写安全
,仅此而已,但是线程安全还有除了读写的其他操作,比如:当一个线程正在get/set时,另一个线程同时进行release操作,可能会直接crash。
setter方法会根据修饰符调用不同方法,其中最后会统一调用reallySetProperty
方法,其中就有atomic和非atomic的操作.
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
if (!atomic) {//没加锁
oldValue = *slot;
*slot = newValue;
} else {//加锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
...
}
using spinlock_t = mutex_tt<LOCKDEBUG>;//可以看到spinlock指向mutexlock
class mutex_tt : nocopy_t {
os_unfair_lock mLock;//互斥
...
}
对于atomic修饰的属性,进行了spinlock_t
加锁处理,但是在前文中提到OSSpinLock已经废弃了,这里的spinlock_t
在底层是通过os_unfair_lock
替代了OSSpinLock
实现的加锁。同时为了防止哈希冲突,还是用了加盐操作。
众所周知,atomic是自旋锁。但这也只是iOS10以前,苹果在iOS10以后因为优先级反转导致的安全问题抛弃了OSSpinLock,改用os_unfair_lock
互斥锁。所以atomic在iOS10以后是互斥锁!!
。系统中自旋锁已经全部改为互斥锁实现了,只是名称一直没有更改,应该是为了兼容iOS10之前系统。参考链接:不再安全的 OSSpinLock
@synchronized:互斥递归
开启汇编调试,会发现@synchronized在执行过程中,会走底层的objc_sync_enter
和 objc_sync_exit
方法。
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//传入不为nil
SyncData* data = id2data(obj, ACQUIRE);//重点 id2data这一步管理了obj和SyncData的映射关系,根据obj获取SyncData
ASSERT(data);
data->mutex.lock();//加互斥递归锁
} else {//传入nil 则什么都不做,加锁不会成功
// @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;
}
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;
在obj存在时,会通过id2data方法,获取SyncData
。进入SyncData的定义,是一个结构体
,主要用来表示一个线程data,类似于链表结构
,有next指向,threadCount,且封装了recursive_mutex_t
属性,可以确认@synchronized确实是一个递归互斥锁
。
总的来说,就是执行时,会实现和当前对象关联
的SyncData,会通过threadCount
和lockCount
记录线程嵌套和锁了几次,同时会有SyncCache
记录缓存的线程。然后用链表存储SyncData
,原因是链表可以方便下一个SyncData
的插入
。当你调用objc_sync_enter(obj)
时,这里也会先去查找缓存SyncCache
,再去用 obj 内存地址的哈希值查找合适的 SyncData
,然后将其上锁
,如果没有找到,则创建新的 SyncData
。当你调用 objc_sync_exit(obj) 时,它查找合适的 SyncData 并将其解锁。
总结id2data步骤:
- 先从当前线程的tls fast cache
快速缓存
中去获取单个SyncData对象。 - 如果1中SyncData未找到,再从
当前线程的缓存
中获取SyncCache
,遍历SyncCacheItem数组,找到对应的SyncData
。 - 如果2中SyncData未找到,再从
全局的哈希表
sDataLists中查找SyncCache,查看其它线程是否已经占用过obj。 - 如果还是没有找到SyncData,则
新建
一个SyncData对象。 - 把新建的SyncData加入到
当前线程缓存里
,或者全局的哈希表
sDataLists中。
你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁
并存储在全局哈希表中。
由于链表查询、缓存的查找以及递归,导致synchronized
是非常耗内存以及性能的,但其使用简单,不必手动管理加锁解锁。
缺点:@synchronized block 在被保护的代码上暗中添加了一个异常处理。为的是同步某对象时如若抛出异常,锁会被释放掉,从而带来额外开销。
很不幸的是在 Swift 中它已经 (或者是暂时) 不存在了。@synchronized 在幕后做的事情是调用了 objc_sync 中的objc_sync_enter
和 objc_sync_exit
方法,并且加入了一些异常判断。因此,在 Swift 中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:
func synchronized(lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
-
synchronized 的 obj 为 nil 怎么办?
加锁操作无效。// @synchronized(nil) does nothing -
synchronized 会对 obj 做什么操作吗?
会为obj生成递归自旋锁,并建立关联,生成 SyncData,存储在当前线程的缓存里或者全局哈希表里。 -
synchronized 和 pthread_mutex 有什么关系?
SyncData里的递归互斥锁,使用 pthread_mutex 实现的。 -
synchronized的
可重入
,即可嵌套,主要是由于lockCount
和threadCount
的搭配
pthread_mutex
pthread_mutex是互斥锁本身
NSLock
NSLock是对pthread_mutex的封装
NSRecursiveLock
NSRecursiveLock是对pthread_mutex的封装
NSConditionLock
NSCondition是对pthread_mutex和pthread_cond的一种封装。