OC底层原理26-锁上

340 阅读4分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

一.锁性能分析

Xnip2021-08-17_21-28-05.jpg 图中锁的性能排序 OSSpinLock(自旋锁) -> dispatch_semaphone(信号量) -> os_unfair_lock(自旋锁)->pthread_mutex_t(互斥锁) -> NSLock(互斥锁) -> NSCondition(条件锁) ->pthread_mutex_t(recursive 互斥递归锁) ->NSRecursiveLock(递归锁)->@synchronized(互斥锁)->NSConditionLock(条件锁)

@synchronized的在release版本优化的很好

二.synchronized的原理分析上

@synchronized(参数){ } 以下的几个问题

参数->self-nil

@synchronized代码块 ->到底是什么

加锁的效果

递归可重入

什么结构

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

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp

下面是main.cpp代码

struct _SYNC_EXIT {
        _SYNC_EXIT(id arg) : sync_exit(arg) {}
        ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
        id sync_exit;
    }

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);
            objc_sync_exit(_sync_obj);
           
        }

    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

通过分析@synchronized (appDelegateClassName){} 执行了objc_sync_enter 又执行objc_sync_exit 接下来添加系统断点看一下

Xnip2021-08-17_22-07-07.jpg

运行

Xnip2021-08-17_22-08-31.jpg

三.sync的原理分析-synData的结构

通过objc源码 找到 objc_sync_enterobjc_sync_exit

int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    //如果objc传nil就走else
    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;
}

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

接下来找到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;

四.synchronized整个数据结构

我们看一下 id2data

Xnip2021-08-17_22-45-30.jpg

查看一下 LOCK_FOR_OBJLIST_FOR_OBJ

Xnip2021-08-17_22-46-26.jpg

sDataLists 全局 哈希表

断点打印 sDataLists显示如下图 Xnip2021-08-17_22-59-34.jpg

SyncList 里面是SyncData

Xnip2021-08-17_23-07-37.jpg 遵循下面拉链法 Xnip2021-08-17_23-06-28.jpg

五.synchronized的原理分析下

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 //tls(Thread Local Storage,本地局部的线程缓存)
    // Check per-thread single-entry fast cache for matching object
    bool fastCacheOccupied = NO;
    //通过KVC方式对线程进行获取 线程绑定的data
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    //如果线程缓存中有data,执行if流程
    if (data) {
        fastCacheOccupied = YES;
        //如果在线程空间找到了data
        if (data->object == object) {
            // Found a match in fast cache.
            uintptr_t lockCount;

            result = data;
            //通过KVC获取lockCount,lockCount用来记录 被锁了几次,即 该锁可嵌套
            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: {
                //objc_sync_enter走这里,传入的是ACQUIRE -- 获取
                lockCount++;//通过lockCount判断被锁了几次,即表示 可重入(递归锁如果可重入,会死锁)
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);//设置
                break;
            }
            case RELEASE:
                //objc_sync_exit走这里,传入的why是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);//判断缓存中是否有该线程
    //如果cache中有,方式与线程缓存一致
    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中清除使用标记
                    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) {//cache中已经找到
            if ( p->object == object ) {//如果不等于空,且与object相似
                result = p;//赋值
                // atomic because may collide with concurrent RELEASE
                OSAtomicIncrement32Barrier(&result->threadCount);//对threadCount进行++
                goto done;
            }
            if ( (firstUnused == NULL) && (p->threadCount == 0) )
                firstUnused = p;
        }
    
        // no SyncData currently associated with object 没有与当前对象关联的SyncData
        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) { //判断是否支持栈存缓存,支持则通过KVC形式赋值 存入tls
            // Save in fast thread cache
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);//lockCount = 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.sync 哈希表 - 拉链法 syncData
  • 2.sDatalist array synclist(objc)
  • 3.objc_sync_enter /exit 对称 递归锁
  • 4.两种锁:tls/cache
  • 5.第一次 syncData 头插法 链表 标记threadCount = 1
  • 6.判断是不是同一个对象
  • 7.是的话TLS->lock++
  • 8.不是的话sync threadCount++
  • 9.lock-- threadCount--
  • Synchronized:可重入 可递归 多线程
  • 1.TLS保障 threadCount 多少条线程对锁对象加锁
  • 2.lock++ 进来多少次

六.synchronized的注意事项

@synchronized (self) { }为什么传参数为self

  • 1.self 生命周期
  • 2.方便存储 释放

下面代码这样写,会有什么问题?

 for (int i = 0; i < 200000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self.testArray = [NSMutableArray array];
        });
    }

运行结果发现,运行就崩溃

Xnip2021-08-17_23-50-26.jpg

崩溃的主要原因是testArray在某一瞬间变成了nil,从@synchronized底层流程知道,如果加锁的对象成了nil,是锁不住的,相当于下面这种情况,block内部不停的retainrelease,会在某一瞬间上一个还未release,下一个已经准备release,这样会导致野指针的产生

可以根据上面的代码,打开edit scheme -> run -> Diagnostics中勾选Zombie Objects ,来查看是否是僵尸对象,结果如下所示

Xnip2021-08-17_23-51-14.jpg

我们一般使用@synchronized (self),主要是因为_testArray的持有者是self

注意:野指针 vs 过渡释放

  • 野指针:是指由于过渡释放产生的指针还在进行操作
  • 过渡释放:每次都会retainrelease