注册

锁的原理(二):@synchronized

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");
}
}
}
});
  • 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信息,当然不仅仅是它:

7b58ca52658740431e35950922e2de5e.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数据结构如下:

cc93d101911711eb5b21f35a73626e3e.png

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
4e53baf8358b3d0538e94344eda4c7b5.png

NSLog@synchronized处断点验证。

  • 1处的验证结果:

b246b015f6e6ee217896feee02ecf961.png

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

  • 2处验证结果:

9703b88cc888e41aec1d98deb227b646.png

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

  • 3处结果验证:
d1f1555ee960ebda1780444622b7c2cb.png

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

  • 4处验证结果:
db14a2bf819b7c0576db03651e009f72.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数据之间关系:


677e5d2f9398ad5e9ecee65680f514db.png096c27d2cfe04f00b735f5f6fe2dcf2e.png


作者:HotPotCat
链接:https://www.jianshu.com/p/a816e8cf3646

0 个评论

要回复文章请先登录注册