锁的原理(二):@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