“这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战”
一.锁性能分析
图中锁的性能排序 OSSpinLock(自旋锁)
-> dispatch_semaphone(信号量)
-> os_unfair_lock(自旋锁)
->pthread_mutex_t(互斥锁)
-> NSLock(互斥锁)
-> NSCondition(条件锁)
->pthread_mutex_t(recursive 互斥递归锁)
->NSRecursiveLock(递归锁)
->@synchronized(互斥锁)
->NSConditionLock(条件锁)
@synchronized
的在release
版本优化的很好
二.synchronized的原理分析上
@synchronized(参数){ }
以下的几个问题
参数->self-nil
@synchronized
代码块 ->到底是什么加锁的效果
递归可重入
什么结构
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
@synchronized (appDelegateClassName) {
}
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp
下面是main.cpp
代码
struct _SYNC_EXIT {
_SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __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);
objc_sync_exit(_sync_obj);
}
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
通过分析@synchronized (appDelegateClassName){}
执行了objc_sync_enter
又执行objc_sync_exit
接下来添加系统断点看一下
运行
三.sync的原理分析-synData的结构
通过objc
源码 找到 objc_sync_enter
和 objc_sync_exit
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
//如果objc传nil就走else
if (obj) {
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();
}
return result;
}
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
接下来找到SyncData
是什么样的结构
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;//单项链表
DisguisedPtr<objc_object> object;//伪装
int32_t threadCount; // number of THREADS using this block 多线程访问
recursive_mutex_t mutex;//递归锁
} SyncData;
四.synchronized整个数据结构
我们看一下 id2data
查看一下 LOCK_FOR_OBJ
和LIST_FOR_OBJ
sDataLists
全局 哈希表
断点打印 sDataLists
显示如下图
SyncList
里面是SyncData
遵循下面拉链法
五.synchronized的原理分析下
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS //tls(Thread Local Storage,本地局部的线程缓存)
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
//通过KVC方式对线程进行获取 线程绑定的data
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
//如果线程缓存中有data,执行if流程
if (data) {
fastCacheOccupied = YES;
//如果在线程空间找到了data
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//通过KVC获取lockCount,lockCount用来记录 被锁了几次,即 该锁可嵌套
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: {
//objc_sync_enter走这里,传入的是ACQUIRE -- 获取
lockCount++;//通过lockCount判断被锁了几次,即表示 可重入(递归锁如果可重入,会死锁)
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);//设置
break;
}
case RELEASE:
//objc_sync_exit走这里,传入的why是RELEASE -- 释放
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);//判断缓存中是否有该线程
//如果cache中有,方式与线程缓存一致
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {//遍历总表
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE://加锁
item->lockCount++;
break;
case RELEASE://解锁
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache 从cache中清除使用标记
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
//第一次进来,所有缓存都找不到
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {//cache中已经找到
if ( p->object == object ) {//如果不等于空,且与object相似
result = p;//赋值
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);//对threadCount进行++
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object 没有与当前对象关联的SyncData
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it 第一次进来,没有找到
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));//创建赋值
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// 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
if (!fastCacheOccupied) { //判断是否支持栈存缓存,支持则通过KVC形式赋值 存入tls
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);//lockCount = 1
} else
#endif
{
// Save in thread cache 缓存中存一份
if (!cache) cache = fetch_cache(YES);//第一次存储时,对线程进行了绑定
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
- 总结
- 1.sync 哈希表 - 拉链法 syncData
- 2.sDatalist array synclist(objc)
- 3.objc_sync_enter /exit 对称 递归锁
- 4.两种锁:tls/cache
- 5.第一次 syncData 头插法 链表 标记threadCount = 1
- 6.判断是不是同一个对象
- 7.是的话TLS->lock++
- 8.不是的话sync threadCount++
- 9.lock-- threadCount--
- Synchronized:可重入 可递归 多线程
- 1.TLS保障 threadCount 多少条线程对锁对象加锁
- 2.lock++ 进来多少次
六.synchronized的注意事项
@synchronized (self) { }
为什么传参数为self
- 1.
self
生命周期 - 2.方便存储 释放
下面代码这样写,会有什么问题?
for (int i = 0; i < 200000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.testArray = [NSMutableArray array];
});
}
运行结果发现,运行就崩溃
崩溃的主要原因是testArray
在某一瞬间变成了nil
,从@synchronized
底层流程知道,如果加锁的对象成了nil
,是锁不住的,相当于下面这种情况,block
内部不停的retain
、release
,会在某一瞬间上一个还未release
,下一个已经准备release
,这样会导致野指针的产生
可以根据上面的代码,打开edit scheme -> run -> Diagnostics
中勾选Zombie Objects
,来查看是否是僵尸对象,结果如下所示
我们一般使用@synchronized (self)
,主要是因为_testArray
的持有者是self
注意:野指针 vs 过渡释放
野指针
:是指由于过渡释放产生的指针还在进行操作过渡释放
:每次都会retain
和release