一、性能分析
网上很多对比八大锁性能的文章,时间大部分比较早。苹果对某些锁内部进行了优化。这篇文章找中会以10
万次数据做对比对主流锁性能进行分析。
1.1 调用情况模拟
OSSpinLock
OSSpinLock
在iOS 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_lock
是iOS10
推出的新类型的锁需要导入头文件<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_t
是linux
下提供的锁,需要导入头文件<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
NSLock
是Foundation
框架提供的锁:
/** 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
打断点查看汇编定位:
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
,参数是obj
与ACQUIRE
。- 然后通过
mutex.lock()
加锁。
- 然后通过
-
obj
为nil
的情况下调用objc_sync_nil
,根据注释does nothing
是一个空实现。
mutex
mutex
是recursive_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
,参数是obj
与RELEASE
。 - 获取到数据进行解锁,解锁成功返回
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_enter
与objc_sync_exit
中都调用了id2data
获取数据,区别是第二个参数,显然id2data
就是数据处理的核心了。
进行代码块折叠后有如下逻辑:
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
,其它平台容量为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
信息,当然不仅仅是它:
-
data
不存在直接返回,create
为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
数据之间关系:
@synchronized
完整调用流程: