iOS-锁的原理分析(一)

前言

当我们提到线程时,就会联想到线程不安全,如何保线程安全以及多线程之间数据访问如何保证不出问题呢,带着这些疑问,我们来介绍一下锁的原理。

1 Synchronized的比较和测试

有人说synchronized锁的耗费的性能是最强的,测试代如下:

   int ro_runTimes = 100000;
    /** OSSpinLock 性能 */
    {
        OSSpinLock ro_spinlock = OS_SPINLOCK_INIT;
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            OSSpinLockLock(&ro_spinlock);          //解锁
            OSSpinLockUnlock(&ro_spinlock);
        }
        double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"OSSpinLock: %f ms",(kc_endTime - ro_beginTime)*1000);
    }
    
    /** dispatch_semaphore_t 性能 */
    {
        dispatch_semaphore_t ro_sem = dispatch_semaphore_create(1);
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            dispatch_semaphore_wait(ro_sem, DISPATCH_TIME_FOREVER);
            dispatch_semaphore_signal(ro_sem);
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"dispatch_semaphore_t: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    
    /** os_unfair_lock_lock 性能 */
    {
        os_unfair_lock ro_unfairlock = OS_UNFAIR_LOCK_INIT;
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            os_unfair_lock_lock(&ro_unfairlock);
            os_unfair_lock_unlock(&ro_unfairlock);
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"os_unfair_lock_lock: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    
    
    /** pthread_mutex_t 性能 */
    {
        pthread_mutex_t ro_metext = PTHREAD_MUTEX_INITIALIZER;
      
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            pthread_mutex_lock(&ro_metext);
            pthread_mutex_unlock(&ro_metext);
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"pthread_mutex_t: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    
    
    /** NSlock 性能 */
    {
        NSLock *ro_lock = [NSLock new];
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            [ro_lock lock];
            [ro_lock unlock];
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"NSlock: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    
    /** NSCondition 性能 */
    {
        NSCondition *ro_condition = [NSCondition new];
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            [ro_condition lock];
            [ro_condition unlock];
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"NSCondition: %f ms",(ro_endTime - ro_beginTime)*1000);
    }

    /** PTHREAD_MUTEX_RECURSIVE 性能 */
    {
        pthread_mutex_t ro_metext_recurive;
        pthread_mutexattr_t attr;
        pthread_mutexattr_init (&attr);
        pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init (&ro_metext_recurive, &attr);
        
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            pthread_mutex_lock(&ro_metext_recurive);
            pthread_mutex_unlock(&ro_metext_recurive);
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    
    /** NSRecursiveLock 性能 */
    {
        NSRecursiveLock *ro_recursiveLock = [NSRecursiveLock new];
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            [ro_recursiveLock lock];
            [ro_recursiveLock unlock];
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"NSRecursiveLock: %f ms",(ro_endTime - ro_beginTime)*1000);
    }
    

    /** NSConditionLock 性能 */
    {
        NSConditionLock *ro_conditionLock = [NSConditionLock new];
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            [ro_conditionLock lock];
            [ro_conditionLock unlock];
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"NSConditionLock: %f ms",(ro_endTime - ro_beginTime)*1000);
    }

    /** @synchronized 性能 */
    {
        double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
        for (int i=0 ; i < ro_runTimes; i++) {
            @synchronized(self) {}
        }
        double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
        NSLog(@"@synchronized: %f ms",(ro_endTime - ro_beginTime)*1000);
    }

经过测试,在不同的环境表现不一样,真机表现比较好,模拟器表现要差点,这说明synchronized这个锁在真机上有一定的优化。synchronized锁在我们的应用中使用率相对比较高。下面我们分析下原理。

2 Synchronized的分析

2.1 Synchronized初探

我们来一份售票的代码,如下:

- (void)ro_testSaleTicket{
 /// 异步售票
    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(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
    }else{
        NSLog(@"当前车票已售罄");
    }

}

ro_testSaleTicket方法中有几个异步售票,这个时候就会产生线程安全的问题,我们可以在saleTicket这个方法中加入

 @synchronized (self) {
        
    }

来处理

  • 圆括号里面,我们传的self,为什么要传self
  • 我们可不可以传nil
  • 大括号的代码块到底是什么
  • 加锁的效果
  • 递归可重入(可以递归,继续synchronized)
  • synchronized的数据结构是什么
    我们带着这些疑问,我们来分析synchronized的原理。
    我们使用命令把我们的demo工程传换cpp,如下:
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
    我们打开main.cpp文件,我们找到发下代码:
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);
}

其中

  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);

这段代码加锁成功的代码,_SYNC_EXIT是一个结构体,我们整理代码就成了以下:

 objc_sync_enter(_sync_obj);
 _sync_exit(_sync_obj);

_sync_exit相当于_SYNC_EXIT的析构,也就是objc_sync_exit(sync_exit);这个函数,相当于:

 objc_sync_enter(_sync_obj);
 objc_sync_exit(_sync_obj)

我们通过符号断点objc_sync_enter,查看它的汇编流程,如图:

1

从这里可以看出是在libobjc.A.dylib中,那我们需要打开objc(818)的源码进分析了,我们在源码中搜下objc_sync_enter,找到如下代码:

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;
}

这里如果obj不存在就走调用else代码,我们看到注释* @synchronized(nil) does nothing这里的synchronized传的nil,什么事情都没做,objc_sync_nil*最终调用以下代码:

#   define BREAKPOINT_FUNCTION(prototype)                             \
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
    prototype { asm(""); }

这里什么都没实现。

 if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        data->mutex.lock();
    }

这段代码才是核心,我们来分析下。
我们先搜下objc_sync_exit这个函数,看下它的源码,如下 :

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;
}

  • // @synchronized(nil) does nothing这里的传nil同样什么也没做。
    objc_sync_enter
    objc_sync_exit*一比较,可以发现就是一进一出,lock跟unlock。
    objc_sync_enter函数中
SyncData* data = id2data(obj, ACQUIRE);


objc_sync_exit函数中

SyncData* data = id2data(obj, RELEASE);  

一比较发现,id2data一个传的是ACQUIRE,一个传的RELEASE,一个加锁,一个释放锁,是对称的。
这两个函数都对SyncData进行操作判断,那么SyncData是什么呢,我们来看下。
经过搜索发现

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

这里可以看出是一个结构体。

  • struct SyncData nextData;*这里很显眼是一个单向链接表结构。
  • DisguisedPtr object;,我们看下它的源码
class DisguisedPtr {
    uintptr_t value;

    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }

 public:
    DisguisedPtr() { }
    DisguisedPtr(T* ptr) 
        : value(disguise(ptr)) { }
    DisguisedPtr(const DisguisedPtr& ptr) 
        : value(ptr.value) { }

    DisguisedPtr& operator = (T* rhs) {
        value = disguise(rhs);
        return *this;
    }
    DisguisedPtr& operator = (const DisguisedPtr& rhs) {
        value = rhs.value;
        return *this;
    }

    operator T* () const {
        return undisguise(value);
    }
    T* operator -> () const { 
        return undisguise(value);
    }
    T& operator * () const { 
        return *undisguise(value);
    }
    T& operator [] (size_t i) const {
        return undisguise(value)[i];
    }

    // pointer arithmetic operators omitted 
    // because we don't currently use them anywhere
};

这里进行了封装,把ptr按照unsigned long类型转换。

  • *int32_t threadCount; * 多条线程保护
  • recursive_mutex_t mutex; 递归锁,不能多线程递归使用,不然会BUG,后面演示。

2.2 synchronized整个数据结构

我们来看下objc_sync_enter这个函数

SyncData* data = id2data(obj, ACQUIRE);

这行代码到底了什么操作,objc_sync_enterobjc_sync_exit都调用了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;

#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

    // Check per-thread cache of already-owned locks for matching object
    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;
        }
    }

    // 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.
    
    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;
        }
    }

    // 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.
    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++;
        }
    }

    return result;
}

代码较复杂,我们一块一块的来分析。
这个函数整个大的代码块

/// 1
if (data) {
 /// 逻辑
}
///21
if (cache) {
/// 逻辑
}
/// 3
lockp->lock();{
/// 逻辑
}
/// 4
lockp->unlock();
    if (result) {
/// 逻辑
}

我们接下来,一段一段的来分析。
#if SUPPORT_DIRECT_THREAD_KEYS需要支持TLS,什么是TLS, 我们来解释下。
线程局部存储(Thread Local Storage, TLS)是操作系统为线程单独提供的私有空间,通常只有有限的容量。Linux系统下通常通过pthread库中的
pthread_key_create()、
pthread_getspecific()、
pthread_setspecific()、
pthread_key_delete()

if(data)if(cache)有两个地方存储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;

这段代码对result赋值操作,继续走,

 done:
    lockp->unlock();

done了之后,又进行了unlock(),这是为什么呢?
这是因为在内部操作的时候,开辟内存空间,内存空间的相关处理,保证线程安全。
上面我们通过分析SyncData,知道它是一个单向链表,为什么呢,为什么会有两次存储?
spinlock_t *lockp = &LOCK_FOR_OBJ(object);是通过object获取了一把锁,#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock这是这个宏定义。
SyncData **listp = &LIST_FOR_OBJ(object);也是通过个获取的。

#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap sDataLists;

这里为什么会有sDataLists
sDataLists是一个静态全局变量。
StripedMap是哈希map,通过下标,哈希有时候会出现冲突,通常我们再次哈希来解决,如果再冲突,我们再次哈希,这是常规做法,这次我们介绍一下拉链法。
sDataLists是静态全局变量,StripedMap其实是哈希结构,也就是全局的哈希表,SyncList的结构如下:

struct SyncList {
    SyncData *data;
    spinlock_t lock;

    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

我们通过LLDB调试下,如图:


2

这里就是sDataLists结构,一共64个数据。

3

第4个数据,说明我们来了一次,这也间接证明这是哈希结构。
synData数据来源于object。
假如我们锁的是self,又来了self锁,又会创建一个synData存储到哈希表中,这个时候关键词都是self,这时候怎么办,这时候会存在哈希冲突的结构,在我们的SyncList不在是一个数据结构,而是通过链表形式存储,因为两两不会冲突,链表结构,不方便查询,但是SyncData是不需要我们查询的,我们只需要加锁,解锁,只需要增删,不需要查询,这就形成了拉链法。

2.3 synchronized的原理分析

我们来分析下synchronized的存储原理。
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
如果这里TLS(栈存储)存在,会走到if(data)里面的逻辑,如果不存在往下走,因为第一次来,会走到

  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;

这里创建一个SyncData,并存起来,
*result->nextData = listp; *listp = result;这里的nextData指向的Listp,这是链表的头插法,也就是从头部插入数据。

/// 如果支持TLS走这个
#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
/// 如果不支持TLS走这个
        {
            // Save in thread cache
            if (!cache) cache = fetch_cache(YES);
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }

接着执行这里存储。
后面进来这里就是走正常的加锁,解锁流程,我们来看下。

 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;

SyncData data = (SyncData )tls_get_direct(SYNC_DATA_DIRECT_KEY);这行代码是获取上一次的数据。
这里
if (data->object == object)
这里跟上一次的object对比,如果等于,就会走加锁解锁的流程,如果不等于就执行以下代码:

    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;
        }
    }

if ( p->object == object )这时的判断照样不会执行,因为我们换一个新的对象,如果是相同的对象必然会进去。
这也证明,虽然syncData不同,但是objc相同,同一个对象会重复加锁,这就是它的可递归可重入。
我们再来分析下,是不是这样的,if (data->object == object)如果这里相等,执行lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);这里拿到锁了多少次,如果ACQUIRElockCount++,再锁一次,如果是RELEASE,执行lockCount--;锁的次数减一,之后进行解锁,if (lockCount == 0)这里有这样的一个判断,如果lockCount==0,当前被解释完成了,执行

  tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount);

在线程的栈存空间对objc的加锁没有了,完成了,这也说明在其它的线程同样有这个操作。这也证实了可多线程加锁。
这样一个案例
1 线程加锁
2 线程加锁
3 线程加锁
4 线程加锁

那么解锁的过程,必然是
4 线程解锁
3 线程解锁
2 线程解锁
1 线程解锁

4线程是不可能解锁3线程,4线程是在自己的线程存储空间,线程之间的存储空间是独立的,所以不可能解没3线程
所以这里threadCount(默认标识是1)具备多线程的,lockCount说明可递归。

(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;
            }

这段代码,如果对象相同,进行内存平栈。
Sync总结:

  • sync全局哈希表-采用拉链法
  • 形成sDataList array存储了synclist,synclist又绑定了objc(需要加锁的对象)

  • objc又封装了objc_sync_enter/objc_sync_exit这两个对称,递归锁
  • 支持两种存储,一种TLS,一种是cache
  • 第一次创建,然后头插法创建链表结构,标记为threadCount为1
  • 第二次判断是不是同一个对象进来
  • 如果是同一个对象,TLS的lockCount++操作
  • 如果在TLS找不到,重新创建一个SyncData并对threadCount++
  • **lock--,threadCount-- **
    Synchronized:具备可重入,可递归,可多线程,有TLS的保障了threadCount,lockCount标识进来多少条线程对这个对象加锁

2.4 synchronized的注意事项

synchronized锁的对象不要出现空,原因上述源码已说明。
synchronized锁的对象的生命周期至少要跟线程代码的对象一样或者比它长,所以一般我们使用self
synchronized锁对象使用self,可以防止多次创建,方便存储和释放。
synchronized锁对象使用self,如果当前的对象锁的很长,这个链表就会很大,就会对拉链有一定的负担,不过一般不会操作的很频繁。
synchronized锁,模拟器与真机耗时之间相差比较大是因为,在模拟器是有64个大小的限制

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

因为这里的判断,如果是真机就是8,64个要耗费长,查的也长时间长。

结语

这篇文章介绍了synchronized锁的原理以及注意事项和坑点,因为Synchronized使用率较高,我们分析的比较详细,后续还会推出锁的相关文章。

你可能感兴趣的:(iOS-锁的原理分析(一))