美文网首页
@synchronized 探究

@synchronized 探究

作者: lth123 | 来源:发表于2021-06-27 16:21 被阅读0次

    一.@synchronized的作用

    @synchronized 结构所做的事情跟锁(lock)类似:它防止不同的线程同时执行同一段代码。但在某些情况下,相比于使用 NSLock 创建锁对象、加锁和解锁来说,@synchronized 用着更方便,可读性更高。

    @implementation ThreadSafeQueue
    {
        NSMutableArray *_elements;
    }
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _elements = [NSMutableArray array];
        }
        return self;
    }
    
    - (void)increment
    {
        @synchronized (self) {
            [_elements addObject:element];
        }
    }
    
    @end
    

    [_elements addObject:element]; 这行代码被加锁了,同时只允许一个线程访问。

    二.@synchronized实现细节

    1. @synchronized (self) 中的self被释放问题

    在这篇文档中,可以看到@synchronize 加了一个异常处理,如果self对象出现异常,锁会被释放掉。

    The object passed to the @synchronized directive is a unique identifier used to distinguish the protected block. If you execute the preceding method in two different threads, passing a different object for the anObj parameter on each thread, each would take its lock and continue processing without being blocked by the other. If you pass the same object in both cases, however, one of the threads would acquire the lock first and the other would block until the first thread completed the critical section.
    As a precautionary measure, the @synchronized block implicitly adds an exception handler to the protected code. This handler automatically releases the mutex in the event that an exception is thrown. This means that in order to use the @synchronized directive, you must also enable Objective-C exception handling in your code. If you do not want the additional overhead caused by the implicit exception handler, you should consider using the lock classes.

    2.@synchronized源码实现
    image.png

    从上面的汇编代码可以看到,@synchronized block会变成 objc_sync_enter 和 objc_sync_exit 的成对儿调用。

    三. objc_sync_enter 和 objc_sync_exit

    在<objc/objc-sync.h>头文件中看到了这两个函数的信息

    /** 
     * Begin synchronizing on 'obj'.  
     * Allocates recursive pthread_mutex associated with 'obj' if needed.
     *  递归锁
     * @param obj The object to begin synchronizing on.
     * 
     * @return OBJC_SYNC_SUCCESS once lock is acquired.  
     */
    OBJC_EXPORT int
    objc_sync_enter(id _Nonnull obj)
        OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);
    
    /** 
     * End synchronizing on 'obj'. 
     * 
     * @param obj The object to end synchronizing on.
     * 
     * @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
     */
    OBJC_EXPORT int
    objc_sync_exit(id _Nonnull obj)
        OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);
    

    从上面的注释中可以看到:

    1.@synchronized 结构在工作时为传入的对象分配了一个递归锁
    2.objc_sync_enter是同步开始的函数
    3.objc_sync_exit是同步结束或者出现错误的函数

    objc_sync_enter 源码实现
    // Begin synchronizing on 'obj'. 
    // Allocates recursive mutex associated with 'obj' if needed.
    // Returns OBJC_SYNC_SUCCESS once lock is acquired.
    //@synchronized 开始加锁
    int objc_sync_enter(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
        
        // 对obj进行判空
        if (obj) {
            // 获取data对象
            // obj 获取data对象的key
            //ACQUIRE 表示获取data对象
            SyncData* data = id2data(obj, ACQUIRE);
            ASSERT(data);
            // 执行mutex lock函数
            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");
            }
            //如果对象为nil,执行objc_sync_nil函数
            objc_sync_nil();
        }
    
        return result;
    }
    

    从上面的源码可以看到 data是从id2data函数中获取的

    id2data源码实现

    通过object返回一个SyncData对象

    返回值SyncData的结构
    typedef struct alignas(CacheLineSize) SyncData {
        struct SyncData* nextData;  // 指向下一个SyncData对象的指针
        DisguisedPtr<objc_object> object; // 记录传入的对象
        int32_t threadCount;  // number of THREADS using this block 线程的数量,
        recursive_mutex_t mutex; // 递归锁
    } SyncData;
    

    SyncData结构体有4个成员变量

    • nextData

    指向另一个 SyncData 对象的指针

    • object

    保存传入object的地址

    • threadCount

    对象中的锁会被一些线程使用或等待,threadCount 就是此时这些线程的数量。它很有用处,因为 SyncData 结构体会被缓存,threadCount==0 就暗示了这个 SyncData 实例可以被复用

    • mutex

    递归锁,与objec关联的锁对象

    从上面可以看到SyncData是链表中的某一个节点,保存了节点信息

    SyncList的结构
    //链表
    struct SyncList {
        SyncData *data;
        spinlock_t lock; 
    
        constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
    };
    

    每个 SyncList 结构体都有个指向 SyncData 节点链表头部的指针,也有一个用于防止多个线程对此列表做并发修改的锁。

    id2data函数的实现
    static SyncData* id2data(id object, enum usage why)
    {
    //    static StripedMap<SyncList> sDataLists;
    //    sDataLists[obj].lock
        // 从SyncList中通过object,获取lock
        spinlock_t *lockp = &LOCK_FOR_OBJ(object);
        // 从SyncData通过object获取data
        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;
                // 保存data
                result = data;
                // 获取count
                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: {
                    // count 加1
                    lockCount++;
                    tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                    break;
                }
                case RELEASE:
                        // 释放锁
                        // count 减1
                    lockCount--;
                    tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                        // 当count == 0时,从cache中移除,出入NULL就是删除
                    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;
    }
    

    objc_sync_exit源码实现

    int objc_sync_exit(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
        
        if (obj) {
            // 传入RELEASE
            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;
    }
    
    objc_sync_enter关键流程分析:

    获取当前线程的缓存链表结构,查看缓存链表是否存在对象object
    如果存在,则执行lockCount++,更新缓存并结束流程
    如果当前线程的缓存链表中未找到对象object缓存,则查看listp总链表结构
    若总链表结构存在对象object,则threadCount++
    若总链表结构不存在对象object,则新建一个SyncData,且将lockCount、threadCount置为1,最后更新缓存

    objc_sync_exit关键流程分析:

    获取当前线程的缓存链表结构,查看缓存链表是否存在对象object
    如果存在,则执行lockCount--
    如果当前的lockCount==0,则threadCount--,更新缓存并结束流程


    参考引用

    不该用@Synchronized的原因
    苹果官网
    关于@Synchronized

    相关文章

      网友评论

          本文标题:@synchronized 探究

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