锁的原理(二):@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,其它平台容量为64。SynData根据前面的分析是一个单向链表, 那么可以得到在哈希冲突的时候是采用拉链法解决的。
增加以下验证代码:
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集合,SyncList的data中存储的是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。- 当
lockCount为0的时候释放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中数据释放了。 syndata的threadCount进行-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的一个list,allocated用于记录开辟的总容量,used记录已经使用的容量。SyncCacheItem存储了一个SyncData以及lockCount。记录的是针对当前线程SyncData被锁了多少次。SyncCacheItem存储的对应于TSL快速缓存的SYNC_COUNT_DIRECT_KEY与SYNC_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信息,当然不仅仅是它:
不存在直接返回,
datacreate为YES的情况下data不会为空。syncCache不存在的情况下,允许创建则进行calloc(初始容量4,这里是创建syncCache),否则返回NULL。syncCache存满(通过allocated与used判断)的情况下进行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_KEYS,key为__PTK_FRAMEWORK_OBJC_KEY0。 - 不支持
SUPPORT_DIRECT_THREAD_KEYS,key为_objc_pthread_key。
TLS快速缓存通过tls_get_direct获取,key是tls_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;
}
}- 遍历开始获取的
SynList找obj对应的SynData。 - 找到的情况下
threadCount + 1,由于在tls(快速以及cache中)没有找到数据,但是在sDataLists中找到了,所以肯定不在同一个线程(那也肯定就不是exit,而是enter了)直接跳转到done。 eixt的逻辑直接跳转到done。- 没有找到但是找到了
threadCount = 0的Syndata,也就是找到了空位(之前使用过,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数据结构如下:

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:
NSLog的@synchronized处断点验证。1处的验证结果:

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

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

仍然进行了拉链obj3 -> obj2 -> obj。
4处验证结果:

这个时候obj对应的SynData的threadCount是2了。
所有验证结果符合分析预期。
四、总结
参数传
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。enter:lockCount++并存储到SYNC_COUNT_DIRECT_KEY。exit:lockCount--并存储到SYNC_COUNT_DIRECT_KEY。lockCount == 0清空SYNC_DATA_DIRECT_KEY,threadCount -1。
TLS cache缓存获取,遍历cache找到对应的SyncData。enter:lockCount++。exit:lockCount--。lockCount == 0替换cache->list对应的值为最后一个,used -1,threadCount -1。
sDataLists全局哈希表获取SyncData:找到的情况下threadCount + 1进入缓存逻辑,没有找到并且存在threadCount = 0则替换object相当于存储了新值。SyncData创建:创建SyncData,赋值object,threadCount初始化为1,创建mutex锁。并且采用头插法将SyncData插入sDataLists对应的SynList头部。SyncData数据缓存:sDataLists添加了或者更新了数据会走到缓存逻辑,缓存逻辑是往TLS快速缓存以及TLS cache缓存添加数据enter:TLS快速缓存不存在的情况下将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数据之间关系:


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