锁的原理(一):@synchronized

一、性能分析

网上很多对比八大锁性能的文章,时间大部分比较早。苹果对某些锁内部进行了优化。这篇文章找中会以10万次数据做对比对主流锁性能进行分析。

1.1 调用情况模拟

OSSpinLock
OSSpinLockiOS 10以后废弃了,不过还可以调用。需要导入头文件<libkern/OSAtomic.h>

int hp_runTimes = 100000;
/** OSSpinLock 性能 */
{
    OSSpinLock hp_spinlock = OS_SPINLOCK_INIT;
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        OSSpinLockLock(&hp_spinlock);//解锁
        OSSpinLockUnlock(&hp_spinlock);
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("OSSpinLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

dispatch_semaphore_t
信号量是GCD提供的:

/** dispatch_semaphore_t 性能 */
{
    dispatch_semaphore_t hp_sem = dispatch_semaphore_create(1);
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        dispatch_semaphore_wait(hp_sem, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_signal(hp_sem);
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("dispatch_semaphore_t: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

os_unfair_lock
os_unfair_lockiOS10推出的新类型的锁需要导入头文件<os/lock.h>

/** os_unfair_lock_lock 性能 */
{
    os_unfair_lock hp_unfairlock = OS_UNFAIR_LOCK_INIT;
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        os_unfair_lock_lock(&hp_unfairlock);
        os_unfair_lock_unlock(&hp_unfairlock);
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent() ;
    printf("os_unfair_lock_lock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

pthread_mutex_t
pthread_mutex_tlinux下提供的锁,需要导入头文件<pthread/pthread.h>:

/** pthread_mutex_t 性能 */
{
    pthread_mutex_t hp_metext = PTHREAD_MUTEX_INITIALIZER;
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        pthread_mutex_lock(&hp_metext);
        pthread_mutex_unlock(&hp_metext);
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("pthread_mutex_t: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

NSLock
NSLockFoundation框架提供的锁:

/** NSlock 性能 */
{
    NSLock *hp_lock = [NSLock new];
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        [hp_lock lock];
        [hp_lock unlock];
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("NSlock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

NSCondition

/** NSCondition 性能 */
{
    NSCondition *hp_condition = [NSCondition new];
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        [hp_condition lock];
        [hp_condition unlock];
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("NSCondition: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

pthread_mutex_t(recursive)

/** PTHREAD_MUTEX_RECURSIVE 性能 */
{
    pthread_mutex_t hp_metext_recurive;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init (&attr);
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init (&hp_metext_recurive, &attr);
    
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        pthread_mutex_lock(&hp_metext_recurive);
        pthread_mutex_unlock(&hp_metext_recurive);
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("PTHREAD_MUTEX_RECURSIVE: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

NSRecursiveLock

/** NSRecursiveLock 性能 */
{
    NSRecursiveLock *hp_recursiveLock = [NSRecursiveLock new];
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        [hp_recursiveLock lock];
        [hp_recursiveLock unlock];
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("NSRecursiveLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

NSConditionLock

/** NSConditionLock 性能 */
{
    NSConditionLock *hp_conditionLock = [NSConditionLock new];
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        [hp_conditionLock lock];
        [hp_conditionLock unlock];
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent() ;
    printf("NSConditionLock: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

@synchronized

/** @synchronized 性能 */
{
    double_t hp_beginTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0 ; i < hp_runTimes; i++) {
        @synchronized(self) {}
    }
    double_t hp_endTime = CFAbsoluteTimeGetCurrent();
    printf("@synchronized: %f ms\n",(hp_endTime - hp_beginTime) * 1000);
}

锁内部没有处理任何逻辑,都执行的空操作,在10万次循环后计算时间差值。

1.2 验证

iPhone 12 pro max 14.3真机测试数据如下:

OSSpinLock: 1.366019 ms
dispatch_semaphore_t: 1.923084 ms
os_unfair_lock_lock: 1.502037 ms
pthread_mutex_t: 1.694918 ms
NSlock: 2.384901 ms
NSCondition: 2.082944 ms
PTHREAD_MUTEX_RECURSIVE: 3.449082 ms
NSRecursiveLock: 3.075957 ms
NSConditionLock: 7.895947 ms
@synchronized: 3.794074 ms

iPhone 12 pro max 14.3模拟器测试数据如下:

OSSpinLock: 1.199007 ms
dispatch_semaphore_t: 1.991987 ms
os_unfair_lock_lock: 1.762986 ms
pthread_mutex_t: 2.611995 ms
NSlock: 2.719045 ms
NSCondition: 2.544045 ms
PTHREAD_MUTEX_RECURSIVE: 4.145026 ms
NSRecursiveLock: 5.039096 ms
NSConditionLock: 8.215070 ms
@synchronized: 10.205030 ms

对比如下:


锁性能对比表

大部分锁在真机上性能表现更好,@synchronized在真机与模拟器中表现差异巨大。也就是说苹果在真机模式下优化了@synchronized的性能。与之前相比目前@synchronized的性能基本能满足要求。

判断一把锁的性能好坏,一般情况下是与pthread_mutex_t做对比(因为底层都是对它的封装)。

二、@synchronized

由于@synchronized使用比较简单,并且目前真机性能也不错。所以先分析它。

2.1售票案例

有如下代码:

@property (nonatomic, assign) NSUInteger ticketCount;

- (void)testTicket {
    self.ticketCount = 10;
    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 < 2; 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 < 5; i++) {
            [self saleTicket];
        }
    });
}

- (void)saleTicket {
    if (self.ticketCount > 0) {
        self.ticketCount--;
        sleep(0.1);
        NSLog(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
    } else {
        NSLog(@"当前车票已售罄");
    }
}

模拟了多线程售票请款,输出如下:

当前余票还剩:6张
当前余票还剩:7张
当前余票还剩:7张
当前余票还剩:7张
当前余票还剩:4张
当前余票还剩:4张
当前余票还剩:3张
当前余票还剩:2张
当前余票还剩:1张
当前余票还剩:0张
当前车票已售罄
当前车票已售罄
当前车票已售罄
当前车票已售罄
当前车票已售罄

可以看到余票数量有重复以及顺序混乱。
saleTicket加上@synchronized就能解决问题:

- (void)saleTicket {
    @synchronized(self) {
        if (self.ticketCount > 0) {
            self.ticketCount--;
            sleep(0.1);
            NSLog(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
        } else {
            NSLog(@"当前车票已售罄");
        }
    }
}

一般参数传递self。那么有以下疑问:

  • 为什么要传self呢?传nil行不行?
  • @synchronized是怎么实现加锁的效果的呢?
  • {}代码块究竟是什么呢?
  • 是否可以递归呢?
  • 底层是什么数据结构呢?

2.2 clang 分析 @synchronized

@synchronized是个系统关键字,那么通过clang还原它的底层实现,为了方便实现在main函数中调用它:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        @synchronized(appDelegateClassName) {
            
        }
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

clang还原后代码如下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    { __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);
}

异常处理不关心,所以核心就是try的逻辑,精简后如下:

id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
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是个结构体的定义,_sync_exit析构的实现是objc_sync_exit(sync_exit),所以@synchronized本质上等价于enter + exit

//@synchronized(appDelegateClassName) {}
//等价
objc_sync_enter(appDelegateClassName);
objc_sync_exit(appDelegateClassName);

它们是定义在objc中的。当然也可以通过对@synchronized打断点查看汇编定位:

image.png

2.3 源码分析

2.3.1 objc_sync_enter

int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        //obj存在的情况下 获取 SyncData
        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
        objc_sync_nil();
    }

    return result;
}
  • obj存在的情况下通过id2data获取SyncData,参数是objACQUIRE
    • 然后通过mutex.lock()加锁。
  • objnil的情况下调用objc_sync_nil,根据注释does nothing是一个空实现。

mutex
mutexrecursive_mutex_t mutex类型,本质上是recursive_mutex_tt

using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
class recursive_mutex_tt : nocopy_t {
    os_unfair_recursive_lock mLock;
    ......
}

typedef struct os_unfair_recursive_lock_s {
    os_unfair_lock ourl_lock;
    uint32_t ourl_count;
} os_unfair_recursive_lock, *os_unfair_recursive_lock_t;

os_unfair_recursive_lock是对os_unfair_lock的封装。所以 @synchronized 是对os_unfair_lock 的封装。

objc_sync_nil
objc_sync_nil的定义如下:

BREAKPOINT_FUNCTION(
    void objc_sync_nil(void)
);

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

替换还原后如下:

OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) 
void objc_sync_nil(void) {
    asm(""); 
}

也就是一个空实现。

2.3.2 objc_sync_exit

int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;//0
    if (obj) {
        //获取 SyncData
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {//没有输出返回错误code - 1
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            //获取到数据先解锁
            bool okay = data->mutex.tryUnlock();
            if (!okay) {//解锁失败返回-1
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }

    return result;
}
  • obj存在的情况下通过id2data获取SyncData,参数是objRELEASE
  • 获取到数据进行解锁,解锁成功返回0,失败返回-1

2.3.3 SyncData 数据结构

SyncData是一个结构体:

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;//下一个节点
    DisguisedPtr<objc_object> object;//obj,@synchronized的参数
    int32_t threadCount;  // number of THREADS using this block 线程数量
    recursive_mutex_t mutex;//锁
} SyncData;
  • nextData指向下一个节点,SyncData是一个单向链表。
  • object存储的是@synchronized的参数,只不过进行了包装。
  • threadCount代表线程数量。支持多线程访问。
  • mutex创建的锁。递归锁只能递归使用不能多线程使用。

三、id2data

objc_sync_enterobjc_sync_exit中都调用了id2data获取数据,区别是第二个参数,显然id2data就是数据处理的核心了。

进行代码块折叠后有如下逻辑:

image.png

syndata要么从TLS获取,要么从cache获取。都没有的情况下进行创建。

3.1 SyncData存储结构

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

//本身也是 os_unfair_lock
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;

可以看到锁和SyncData都是从sDataLists获取的(hash map结构,存储的是SyncList),SyncList定义如下:

struct SyncList {
    SyncData *data;
    spinlock_t lock;
    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

StripedMap定义如下:

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

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];
    ......
}

iOS真机上容量为8,其它平台容量为64SynData根据前面的分析是一个单向链表, 那么可以得到在哈希冲突的时候是采用拉链法解决的。

增加以下验证代码:

HPObject *obj = [HPObject alloc];
HPObject *obj2 = [HPObject alloc];
HPObject *obj3 = [HPObject alloc];
dispatch_async(dispatch_queue_create("HotpotCat", DISPATCH_QUEUE_CONCURRENT), ^{
    @synchronized (obj) {
        NSLog(@"obj");
        @synchronized (obj2) {
            NSLog(@"obj2");
            @synchronized (obj3) {
                NSLog(@"obj3");
            }
        }
    }
});

断点验证:


image.png
  • sDataLists包装了array,其中存储的是SyncList集合,SyncListdata中存储的是synData

3.2 从 TLS 获取 SyncData

  bool fastCacheOccupied = NO;//后续存储的时候用
    //对 pthread_getspecific 的封装,针对线程中第一次调用 @synchronized 是获取不到数据的。
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;
        //判断要查找的与存储的object是不是同一个。
        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: {//enter 的时候 lockCount + 1,并且存储count到tls
                lockCount++;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            case RELEASE: //exit的时候 lockCount - 1,并且存储count到tls
                lockCount--;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                //当 count 减少到 0 的情况下清除对应obj的SynData,这里并没有清空count,count在存储新objc的时候直接赋值为1
                if (lockCount == 0) {
                    // remove from fast cache
                    tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    // atomic because may collide with concurrent ACQUIRE
                    //threadCount - 1
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }
  • 通过tls_get_direct(是对_os_tsd_get_direct的封装)获取当前线程存储的SynData数据。
  • 在数据存在的情况下判断标记fastCacheOccupied存在。
  • 判断tls存储的数据是不是当前对象。是当前对象则进行进一步处理,否则结束tls逻辑。
  • 获取对象加锁的次数lockCount
  • enter逻辑:lockCount++并存储在tls
  • exit逻辑:lockCount--并存储在tls
    • lockCount0的时候释放SynData,直接在tls中置为NULL
    • 并且threadCount - 1

线程局部存储(Thread Local Storage,TLS): 是操作系统为线程单独提供的私有空间,通常只有有限的容量。
Linux系统下通常通过pthread库中的相关方法进行操作:
pthread_key_create()
pthread_getspecific()
pthread_setspecific()
pthread_key_delete()

3.3 从 Cache 获取 SyncData

tls中没有找到SynData的时候会去Cache中找:

    //获取线程缓存,参数NO 当缓存不存在的时候不进行创建。
    SyncCache *cache = fetch_cache(NO);
    if (cache) {
        unsigned int i;
        for (i = 0; i < cache->used; i++) {
            SyncCacheItem *item = &cache->list[i];
            //找到obj对应的 item
            if (item->data->object != object) continue;

            // Found a match.
            //获取SynData
            result = item->data;
            if (result->threadCount <= 0  ||  item->lockCount <= 0) {
                _objc_fatal("id2data cache is buggy");
            }
                
            switch(why) {
            case ACQUIRE://enter lockCount + 1
                item->lockCount++;
                break;
            case RELEASE://exit lockCount - 1
                item->lockCount--;
                if (item->lockCount == 0) {//lockCount = 0 的时候 从cache中移除i的元素,将最后一个元素存储到原先i的位置。used - 1。也就是最后一个位置被标记为未使用了。
                    // remove from per-thread cache
                    cache->list[i] = cache->list[--cache->used];
                    // atomic because may collide with concurrent ACQUIRE
                    //threadCount - 1
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }
  • 通过fetch_cache(是对pthread_getspecific的封装)找SyncCache,由于是读取数据,所以找不到的情况下这里不创建。
  • 遍历cache已使用的空间找到obj对应的SyncCacheItem
  • enter的情况下item->lockCount++
  • exit情况下item->lockCount--
    • item->lockCount == 0的时候将cache中这个item替换为cache中最后一个,used -1标记cache中使用的数量,这样就将cache中数据释放了。
    • syndatathreadCount进行-1

3.3.1 SyncCache

typedef struct {
    SyncData *data;//数据
    unsigned int lockCount;  // 被当前线程加锁次数
} SyncCacheItem;

typedef struct SyncCache {
    unsigned int allocated;//总容量
    unsigned int used;//已使用
    SyncCacheItem list[0];//列表
} SyncCache;
  • SyncCache中存储的是SyncCacheItem的一个listallocated用于记录开辟的总容量,used记录已经使用的容量。
  • SyncCacheItem存储了一个SyncData以及lockCount。记录的是针对当前线程SyncData被锁了多少次。SyncCacheItem存储的对应于TSL快速缓存的SYNC_COUNT_DIRECT_KEYSYNC_DATA_DIRECT_KEY

3.3.2 fetch_cache

static SyncCache *fetch_cache(bool create)
{
    _objc_pthread_data *data;
    //creat用来处理是否新建。
    data = _objc_fetch_pthread_data(create);
    //data不存在直接返回,create为YES的情况下data不会为空
    if (!data) return NULL;
    //syncCache不存在
    if (!data->syncCache) {
        if (!create) {//不允许创建直接返回 NULL
            return NULL;
        } else {
            //允许创建直接 calloc 创建,初始容量为4.
            int count = 4;
            data->syncCache = (SyncCache *)
                calloc(1, sizeof(SyncCache) + count*sizeof(SyncCacheItem));
            data->syncCache->allocated = count;
        }
    }

    // Make sure there's at least one open slot in the list.
    //存满的情况下扩容 2倍扩容。
    if (data->syncCache->allocated == data->syncCache->used) {
        data->syncCache->allocated *= 2;
        data->syncCache = (SyncCache *)
            realloc(data->syncCache, sizeof(SyncCache) 
                    + data->syncCache->allocated * sizeof(SyncCacheItem));
    }

    return data->syncCache;
}
  • 通过_objc_fetch_pthread_data获取_objc_pthread_data_objc_pthread_data存储了SyncCache信息,当然不仅仅是它:
    image.png
  • data不存在直接返回,createYES的情况下data不会为空。
  • syncCache不存在的情况下,允许创建则进行calloc(初始容量4,这里是创建syncCache),否则返回NULL
  • syncCache存满(通过allocatedused判断)的情况下进行2被扩容。

_objc_fetch_pthread_data

_objc_pthread_data *_objc_fetch_pthread_data(bool create)
{
    _objc_pthread_data *data;
    //pthread_getspecific TLS_DIRECT_KEY
    data = (_objc_pthread_data *)tls_get(_objc_pthread_key);
    if (!data  &&  create) {
        //允许创建的的情况下创建
        data = (_objc_pthread_data *)
            calloc(1, sizeof(_objc_pthread_data));
        //保存
        tls_set(_objc_pthread_key, data);
    }

    return data;
}
  • 通过tls_get获取_objc_pthread_data,不存在并且允许创建的情况下进行calloc创建_objc_pthread_data
  • 创建后保存到tls

这里的cache也是存储在tls,与tls_get_direct的区别要看二者存取的逻辑,一个调用的是tls_get_direct,一个是tls_get

#if defined(__PTK_FRAMEWORK_OBJC_KEY0)
#   define SUPPORT_DIRECT_THREAD_KEYS 1
#   define TLS_DIRECT_KEY        ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY0)
#   define SYNC_DATA_DIRECT_KEY  ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY1)
#   define SYNC_COUNT_DIRECT_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY2)
#   define AUTORELEASE_POOL_KEY  ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY3)
# if SUPPORT_RETURN_AUTORELEASE
#   define RETURN_DISPOSITION_KEY ((tls_key_t)__PTK_FRAMEWORK_OBJC_KEY4)
# endif
#else
#   define SUPPORT_DIRECT_THREAD_KEYS 0
#endif

#if SUPPORT_DIRECT_THREAD_KEYS
#define _objc_pthread_key TLS_DIRECT_KEY
#else
static tls_key_t _objc_pthread_key;
#endif

//key _objc_pthread_key
static inline void *tls_get(tls_key_t k) { 
    return pthread_getspecific(k);
}

//key SYNC_DATA_DIRECT_KEY 与  SYNC_COUNT_DIRECT_KEY
static inline void *tls_get_direct(tls_key_t k)
{ 
    ASSERT(is_valid_direct_key(k));

    if (_pthread_has_direct_tsd()) {
        return _pthread_getspecific_direct(k);
    } else {
        return pthread_getspecific(k);
    }
}

__header_always_inline int
_pthread_has_direct_tsd(void)
{
#if TARGET_IPHONE_SIMULATOR
    return 0;
#else
    return 1;
#endif
}

__header_always_inline void *
_pthread_getspecific_direct(unsigned long slot)
{
#if TARGET_IPHONE_SIMULATOR
    return pthread_getspecific(slot);
#else
    return _os_tsd_get_direct(slot);
#endif
}

__attribute__((always_inline))
static __inline__ void*
_os_tsd_get_direct(unsigned long slot)
{
    return _os_tsd_get_base()[slot];
}
  • _objc_pthread_data通过pthread_getspecific获取缓存数据,key的类型是tls_key_t
    • 如果支持SUPPORT_DIRECT_THREAD_KEYSkey__PTK_FRAMEWORK_OBJC_KEY0
    • 不支持SUPPORT_DIRECT_THREAD_KEYSkey_objc_pthread_key
  • TLS快速缓存通过tls_get_direct获取,keytls_key_t类型。
    • SynData对应的key__PTK_FRAMEWORK_OBJC_KEY1
    • lockCount对应的key__PTK_FRAMEWORK_OBJC_KEY2
    • iOS模拟器通过pthread_getspecific获取
    • 其它通过_os_tsd_get_direct获取,调用的是_os_tsd_get_base(),不同架构对应不同汇编指令:
__attribute__((always_inline, pure))
static __inline__ void**
_os_tsd_get_base(void)
{
#if defined(__arm__)
    uintptr_t tsd;
    __asm__("mrc p15, 0, %0, c13, c0, 3\n"
                "bic %0, %0, #0x3\n" : "=r" (tsd));
    /* lower 2-bits contain CPU number */
#elif defined(__arm64__)
    uint64_t tsd;
    __asm__("mrs %0, TPIDRRO_EL0\n"
                "bic %0, %0, #0x7\n" : "=r" (tsd));
    /* lower 3-bits contain CPU number */
#endif

    return (void**)(uintptr_t)tsd;
}

3.4 从sDataLists获取SynData

    //sDataLists 中找 Syndata
    {
        SyncData* p;
        SyncData* firstUnused = NULL;
        //从SynList链表中查找SynData
        for (p = *listp; p != NULL; p = p->nextData) {
            if ( p->object == object ) {
                result = p;//找到
                // atomic because may collide with concurrent RELEASE
                //threadCount + 1,由于在上面线程缓存和tls的查找中没有找到,但是在 sDataLists 中找到了。所以肯定不是同一个线程了(那也肯定就不是exit,而是enter了),线程数量+1。
                OSAtomicIncrement32Barrier(&result->threadCount);
                goto done;
            }
            //没有找到的情况下找到了空位。
            if ( (firstUnused == NULL) && (p->threadCount == 0) )
                firstUnused = p;
        }
    
        // no SyncData currently associated with object
        //是exit就直接跳转到done的逻辑
        if ( (why == RELEASE) || (why == CHECK) )
            goto done;
    
        // an unused one was found, use it
        //找到一个未使用的(也有可能是之前使用过,threadCount现在变为0了),直接存储当前objc数据(这里相当于释放了sDataLists中的旧数据)。
        if ( firstUnused != NULL ) {
            result = firstUnused;
            //替换object
            result->object = (objc_object *)object;
            result->threadCount = 1;
            goto done;
        }
    }
  • 遍历开始获取的SynListobj对应的SynData
  • 找到的情况下threadCount + 1,由于在tls(快速以及cache中)没有找到数据,但是在sDataLists中找到了,所以肯定不在同一个线程(那也肯定就不是exit,而是enter了)直接跳转到done
  • eixt的逻辑直接跳转到done
  • 没有找到但是找到了threadCount = 0Syndata,也就是找到了空位(之前使用过,threadCount现在变为0了)。
    • 直接存储当前objc数据到synData中(这里相当于释放了sDataLists中的旧数据)。threadCount标记为1

3.5 创建 SyncData

tls中没有快速缓存、也没cache、并且sDataLists中没有数据也没有空位:

posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
//对象本身
result->object = (objc_object *)object;
//持有线程数初始化为1
result->threadCount = 1;
//创建锁
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
//头插法
result->nextData = *listp;
//这里 sDataLists 中的 SynList就赋值了。
*listp = result;
  • 开辟一个SyncData大小的内存并进行对齐。
  • 设置object以及threadCount
  • 创建mutex锁。
  • 头插法将创建的SynData插入SynList中。也就相当于将数据存入sDataLists中。nextData存在的情况是发生了哈希冲突。

3.6 done 缓存存储逻辑

    //数据存储
    if (result) {//有result,无论是创建的还是从 sDataLists 获取的。
        // Only new ACQUIRE should get here.
        // All RELEASE and CHECK and recursive ACQUIRE are 
        // handled by the per-thread caches above.
        if (why == RELEASE) {//exit不进行任何操作
            // 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
        //TLS 快速缓存不存在,存储到快速缓存。
        if (!fastCacheOccupied) {//
            // Save in fast thread cache
            //存储Syndata
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            //存储count为1
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else 
#endif
        //cache存储 不支持 tls 快速缓存 或者 tls快速缓存存在的情况下
        {
            // Save in thread cache
            //获取SyncCache,不存在的时候进行创建
            if (!cache) cache = fetch_cache(YES);
            //将result放入list的最后一个元素,SyncCacheItem 中存储 result 以及 lockCount
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }
  • exit的时候不进行任何操作:
    • TLS快速缓存会在获取缓存的时候进行释放。并且threadCount -1
    • cache逻辑会进行替换数据(相当于释放),并且threadCount -1
    • sDataLists获取数据逻辑本身不释放,会根据threadCount = 0找到空位进行替换,相当于释放。
  • 在支持快速缓存并且快速缓存不存在的情况下,将创建的SynData以及lockCount = 1存储到TLS快速缓存中。
  • 在不支持快速缓存或者快速缓存已经有值了的情况下将SynData构造SyncCacheItem存入SyncCache中。
  • 也就是说SynData只会在快速缓存与Cache中存在一个,同时会存储在sDataLists中。

3.7 验证

3.7.1 @synchronized 数据结构

根据源码分析@synchronized数据结构如下:

@synchronized数据存储结构

3.7.2 验证

有如下代码:

HPObject *obj = [HPObject alloc];
HPObject *obj2 = [HPObject alloc];
HPObject *obj3 = [HPObject alloc];
dispatch_async(dispatch_queue_create("HotpotCat", DISPATCH_QUEUE_CONCURRENT), ^{
    @synchronized (obj) {
        @synchronized (obj) {
            @synchronized (obj) {
                //obj lockCount = 3 threadCount = 1
                NSLog(@"1 = %p",obj);
                @synchronized (obj2) {
                    //obj2 lockCount = 1 threadCount = 1,有可能存在拉链
                    NSLog(@"2 = %p",obj2);
                    @synchronized (obj3) {
                        //obj3 threadCount = 1, lockCount = 1,必然存在拉链(为了方便验证源码强制修改StripeCount为2)
                        NSLog(@"3 = %p",obj3);
                        dispatch_async(dispatch_queue_create("HotpotCat1", DISPATCH_QUEUE_CONCURRENT), ^{
                            @synchronized (obj) {
                                //obj threadCount = 2,一个线程的 lockCount = 3 另外一个 lockCount = 1
                                NSLog(@"4 = %p",obj);
                            }
                        });
                        //为了让 @synchronized 不exit
                        sleep(10);
                    }
                }
            }
        }
    }
});

do {
    
} while (1);

由于源码是mac工程,在main函数中写一个死循环。为了方便验证将源码中StripeCount改为2

image.png

NSLog@synchronized处断点验证。

  • 1处的验证结果:

    image.png

    lockCount = 3threadCount = 1,并且sDataLists中存储的与快速缓存中是同一个SynData地址。符合预期。

  • 2处验证结果:

    image.png

可以看到这个时候第二个元素已经进行了拉链,并且obj2在链表的头结点。

  • 3处结果验证:
    image.png

仍然进行了拉链obj3 -> obj2 -> obj

  • 4处验证结果:
    image.png

这个时候obj对应的SynDatathreadCount2了。

所有验证结果符合分析预期。

四、总结

  • 参数传nil没有做任何事情。传self在使用过程中不会被释放,并且同一个类中如果都用self底层只会存在一个SynData

  • @synchronized底层是封装的os_unfair_lock

  • objc_sync_enter中加锁,objc_sync_exit中解锁。

  • @synchronized加锁的数据信息都存储在sDataLists全局哈希表中。同时还有TLS快速缓存(一个SynData数据,通常是第一个,释放后会存放新的)以及线程缓存(一组SyncData数据)。这两个缓存互斥,同一个SyncData只存在其中一个)

  • id2data获取SynData流程:

    • TLS快速缓存获取(SYNC_COUNT_DIRECT_KEY),obj对应的SyncData存在的情况下获取SYNC_COUNT_DIRECT_KEY对应的lockCount
      • enterlockCount++并存储到SYNC_COUNT_DIRECT_KEY
      • exitlockCount--并存储到SYNC_COUNT_DIRECT_KEYlockCount == 0清空SYNC_DATA_DIRECT_KEYthreadCount -1
    • TLS cache缓存获取,遍历cache找到对应的SyncData
      • enterlockCount++
      • exitlockCount--lockCount == 0替换cache->list对应的值为最后一个,used -1threadCount -1
    • sDataLists全局哈希表获取SyncData:找到的情况下threadCount + 1进入缓存逻辑,没有找到并且存在threadCount = 0则替换object相当于存储了新值。
    • SyncData创建:创建SyncData,赋值objectthreadCount初始化为1,创建mutex锁。并且采用头插法将SyncData插入sDataLists对应的SynList头部。
    • SyncData数据缓存:sDataLists添加了或者更新了数据会走到缓存逻辑,缓存逻辑是往TLS快速缓存以及TLS cache缓存添加数据
      • enterTLS快速缓存不存在的情况下将SyncData存储快速缓存,否则存入cache缓存的尾部。
      • exit:直接return
  • lockCount是针对单个线程而言的,当lockCount = 0的时候对数据进行释放

    • TLS快速缓存是直接设置为NULL(只有一个SyncData
    • TLS cache缓存是直接用最后一个数据进行替换(一组SyncData),然后used -1进行释放
    • 同时threadCount - 1相当于当前线程释放。
  • threadCount是针对跨线程的,在threadCount = 0的时候并不立即释放,而是在下次插入数据的时候进行替换。sDataLists保存所有的数据。

  • lockCount@synchronized可重入可递归的原因,threadCount@synchronized可跨线程的原因。

@synchronized数据之间关系:

@synchronized数据关联关系

@synchronized完整调用流程:

@synchronized流程

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 锁的种类 借用网上的一张有关锁性能的对比图,如下所示: 从上图中我们可以看出来,锁大概可以分为以下几种: 1.:在...
    含笑州阅读 996评论 0 0
  • 1、八大锁效率 八大锁分别:自璇所:OSSpinLock。在iOS10以后该锁被重写,会在堵塞时进行休眠;互斥锁:...
    Henry________阅读 787评论 0 4
  • 1、锁的归类 锁的分类只有两大类自旋锁和和互斥锁。这两大类下又分成很多不同的小类。了解锁之前建议先了解一下线程及线...
    希尔罗斯沃德_董阅读 1,964评论 2 4
  • iOS之武功秘籍 文章汇总[https://www.jianshu.com/p/07991e5b1c30] 写在前...
    長茳阅读 575评论 0 2
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,033评论 0 4