作者: 生产八哥 | 来源:发表于2021-02-02 09:47 被阅读0次

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_enterobjc_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,会通过threadCountlockCount记录线程嵌套和锁了几次,同时会有SyncCache记录缓存的线程。然后用链表存储SyncData,原因是链表可以方便下一个SyncData插入。当你调用objc_sync_enter(obj)时,这里也会先去查找缓存SyncCache,再去用 obj 内存地址的哈希值查找合适的 SyncData,然后将其上锁,如果没有找到,则创建新的 SyncData。当你调用 objc_sync_exit(obj) 时,它查找合适的 SyncData 并将其解锁。

总结id2data步骤:

  1. 先从当前线程的tls fast cache快速缓存中去获取单个SyncData对象。
  2. 如果1中SyncData未找到,再从当前线程的缓存中获取SyncCache,遍历SyncCacheItem数组,找到对应的SyncData
  3. 如果2中SyncData未找到,再从全局的哈希表sDataLists中查找SyncCache,查看其它线程是否已经占用过obj。
  4. 如果还是没有找到SyncData,则新建一个SyncData对象。
  5. 把新建的SyncData加入到当前线程缓存里,或者全局的哈希表sDataLists中。

你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在全局哈希表中。

由于链表查询、缓存的查找以及递归,导致synchronized是非常耗内存以及性能的,但其使用简单,不必手动管理加锁解锁。

缺点:@synchronized block 在被保护的代码上暗中添加了一个异常处理。为的是同步某对象时如若抛出异常,锁会被释放掉,从而带来额外开销。

很不幸的是在 Swift 中它已经 (或者是暂时) 不存在了。@synchronized 在幕后做的事情是调用了 objc_sync 中的objc_sync_enterobjc_sync_exit方法,并且加入了一些异常判断。因此,在 Swift 中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:

func synchronized(lock: AnyObject, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}
  1. synchronized 的 obj 为 nil 怎么办?
    加锁操作无效。// @synchronized(nil) does nothing

  2. synchronized 会对 obj 做什么操作吗?
    会为obj生成递归自旋锁,并建立关联,生成 SyncData,存储在当前线程的缓存里或者全局哈希表里。

  3. synchronized 和 pthread_mutex 有什么关系?
    SyncData里的递归互斥锁,使用 pthread_mutex 实现的。

  4. synchronized的可重入,即可嵌套,主要是由于lockCountthreadCount的搭配


pthread_mutex

pthread_mutex是互斥锁本身

NSLock

NSLock是对pthread_mutex的封装

NSRecursiveLock

NSRecursiveLock是对pthread_mutex的封装

NSConditionLock

NSCondition是对pthread_mutex和pthread_cond的一种封装。

相关文章

网友评论

      本文标题:

      本文链接:https://www.haomeiwen.com/subject/ybajtltx.html