线程缓存查找流程
代码语言:javascript复制//2.从线程缓存中查找如果fetch_cache(NO)参数是NO表示去只是去查找
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
//遍历缓存中的 SyncCacheItem
for (i = 0; i < cache->used; i ) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;//如果线程缓存中查找到item将item->data赋值给result
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
// 将当前的item中的数据设置为空的数据,然后cache->used被使用的个数进行减减
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
// 线程threadCount个数进行减一
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
//如果线程缓存中查询到就直接返回result
return result;
}
}
(滑动显示更多)
线程缓存的查找流程和tls查找流程基本相似,的就是数据结构的类型不同。在线程缓存中通过遍历循环查找需要加锁的对象,详细的流程上面注释的很清楚。
- fetch_cache探究
static SyncCache *fetch_cache(bool create)
{
_objc_pthread_data *data;
//查询或者创建线程数据
data = _objc_fetch_pthread_data(create);
if (!data) return NULL;//如果data = nil 返回NULL
if (!data->syncCache) {
if (!create) {
return NULL;
} else {
//首次创建 4 个 SyncCacheItem
int count = 4;
//开辟内存空间
data->syncCache = (SyncCache *)
calloc(1, sizeof(SyncCache) count*sizeof(SyncCacheItem));
//初始化的个数等于 4
data->syncCache->allocated = count;
}
}
// Make sure there's at least one open slot in the list.
// 下面进行扩容操作如果初始的个数和使用的个数相等 则进行两倍扩容
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;
}
(滑动显示更多)
- 根据create去查找线程数据_objc_pthread_data,如果data不存在直接返回NULL
- 如果data->syncCache不存在且create== YES去为SyncCache和SyncCacheItem开辟内存空间第一次开辟4个SyncCacheItem,初始化的个数allocated = 4。每个SyncCacheItem里面的默认数据都是0
- 如果初始化的个数和使用的个数相进行2倍扩容。返回SyncCache
_objc_fetch_pthread_data探究
代码语言:javascript复制 _objc_pthread_data *_objc_fetch_pthread_data(bool create)
{
_objc_pthread_data *data;
//先去tls查找线程数据,如果data存在直接返回
//如果data不存在且create = NO 返回nil
data = (_objc_pthread_data *)tls_get(_objc_pthread_key);
//如果data不存在且create = YES 则去开辟内存创建_objc_pthread_data
if (!data && create) {
data = (_objc_pthread_data *)
calloc(1, sizeof(_objc_pthread_data));
tls_set(_objc_pthread_key, data);
}
return data;
}
(滑动显示更多)
_objc_fetch_pthread_data流程:
- 在tls中查找_objc_pthread_data,如果查询到直接返回data
- 如果data不存在且create = NO,返回nil
- 如果data不存在且create = YES,则去开辟内存创建_objc_pthread_data
- 所以在create = NO只会去tls中查找data,create = YES先去查找data,如果没有就去开 辟内存创建data
多线程加锁同一对象
为什么说下面这段代码是多线程加锁同意对象呢,因为如果是单个线程内存操作会直接走上面的tls查找流程和线程缓存查找流程:
代码语言:javascript复制lockp->lock();
//多线程操作流程
{
SyncData* p;
SyncData* firstUnused = NULL;
//哈希表中的SyncList的data如果有值即*listp有值
//遍历循环单向链表中的SyncData
for (p = *listp; p != NULL; p = p->nextData) {
//查询到需要加锁的对象
if ( p->object == object ) {
result = p;//赋值操作
// atomic because may collide with concurrent RELEASE
//线程数threadCount进行加1操作,threadCount多个线程对该对象进行加锁操作
OSAtomicIncrement32Barrier(&result->threadCount);
//跳转到done流程。此时的result还是存在各个线程的tls中或者线程缓存中
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
...
}
(滑动显示更多)
多线程操作流程
- 首先会根据object在哈希表中找到下标对应的SyncList,然后判断SyncList的data有值
- 如果SyncList的data有值中有值,则在单向链表找到相应的加锁对象,进行threadCount 操作,然后跳转done流程,在done流程里存储在对应线程的tls中或者线程缓存中
创建SyncData
代码语言:javascript复制posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
//赋值操作
result->object = (objc_object *)object;
result->threadCount = 1;//默认 threadCount = 1
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);//创建一个递归锁
result->nextData = *listp; //哈希下标如果一样头插法形成链表的形式
*listp = result;//更新哈希表中的值
(滑动显示更多)
第一次给对象加锁时,会创建一个SyncData,简单的说就是一个SyncData绑定一个对象,一个对象有且只有一个SyncData。
如果不同对象哈希的下标是一样的这样就会形成单向链表,而插入的方式采用的是头插法。
done流程
代码语言:javascript复制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
// fastCacheOccupied = NO 表示当前线程的tls里面没有数据
// 此时把第一次需要加锁创建的SyncData和lockCout = 1 存储在tls
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{ //当在同一线程对另一个对象进行加锁注意(上一个加锁的对象没有进行解锁)
//此时的SyncData和lockCount存储在线程缓存中
// 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;
(滑动显示更多)
done流程
- 当前线程第一个进行加锁操作的对象,此时会把SyncData和lockCout=1 存储在tls,而且当前线程的tls不会改变除非解锁。一个线程的tls只存储第一次加锁的SyncData和lockCout
- 当在同一线程对另一个对象进行加锁注意(上一个加锁的对象没有进行解锁),此时的SyncData和lockCount存储在线程缓存中
至此整个id2data方法的源码已经探究完,下面通过案例分析下。
案例分析
单线程单对象递归加锁
代码语言:javascript复制int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
@synchronized (p) {
NSLog(@"p第一次进来");
@synchronized (p) {
NSLog(@"p第二次进来");
@synchronized (p ) {
NSLog(@"p第三次进来");
};
};
};
return 0;
}
(滑动显示更多)
在第一个@synchronized打下断点进行调试
第一次对对象加锁*listp、data以及cache都是空,说哈希表中和tls以及线程缓存中都没有查找到。那么就会进入创建SyncData流程,为了方便测试我把在非真机状态下的 StripeCount = 2。
图中显示此时的哈希表中的数据都是空的没有数据,断点往下走一步,继续调试。
*listp = result更新哈希表中data的值,此时result的nextData等于*listp,*listp前面打印的值是空的,所以result的nextData也是空的,*listp = result以后此时*listp已经被赋值。继续调试走到done流程:
图中断点显示此时第一次创建的SyncData和lockCount存储在tls中
在第二个@synchronized打下断点进行调试:
对同一个对象再次加锁此时*listp和data都有值,那么此时会走到tls查找data的流程。
lockCount = 2说明又加锁一次,然后更新tls中lockCount的值,然后直接返回result。
在第三个@synchronized打下断点进行调试:
很明显在单线程中对同一个对象进行递归加锁,SyncData和lockCount存储在tls,而没有缓存的事。
单线程多对象递归加锁
代码语言:javascript复制int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
LWPerson * p1 = [LWPerson alloc];
LWPerson * p2 = [LWPerson alloc];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@synchronized (p) {
NSLog(@"p第一次进来");
@synchronized (p1) {
NSLog(@"p1第二次进来");
@synchronized (p2) {
NSLog(@"p2第三次进来");
};
};
};
});
do {
} while (1);
}
return 0;
}
(滑动显示更多)
在第一个@synchronized打下断点进行调试。
对p对象进行加锁,此时创建的SyncData和lockCount存储在tls中,这个上面已经探究过了。
在第二个@synchronized打下断点进行调试:
*listp有值说明p和p1的哈希下标是一样的,此时cache中也没有数据,tls中的是有data不过和p1不是同一个对象,所以此时会重新创建SyncData,然后用头插法的方式形成单向链表。
最新创建的SyncData放在拉链的最前面,此时p1的SyncData的nextData存储着p的SyncData地址:
因为当前线程的tls中是有data的而且不会改变,所以fastCacheOccupied = YES,在走done流程就会存储在线程缓存中,如果后面还有其它对象需要加锁,也都会储存在缓存中。解锁的时候缓存的数据会被清空。
多线程递归加锁
代码语言:javascript复制int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
LWPerson * p1 = [LWPerson alloc];
dispatch_queue_t queue = dispatch_queue_create("lw", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10;i ) {
dispatch_async(queue, ^{
@synchronized (p) {
NSLog(@"p第一次进来");
@synchronized (p1) {
NSLog(@"p第一次进来");
};
};
});
}
do {
} while (1);
}
return 0;
}
(滑动显示更多)
在 @synchronized (p1)打下断点进行调试:
哈希表中只有一个SyncList有值,而且SyncData里面的值threadCount = 10,说明走了多线程的流程。
而nextData的值为空,说明此时只有一个对象进行了多线程加锁,如果多线程多对象实际就是每个对象走单个对象的加锁流程。
单向链表图 后面补上。
@synchronized总结
objc_sync_exit流程和objc_sync_enter流程走的是一样的只不过一个是加锁一个是解锁
@synchronized底层是链表查找、缓存查找以及递归,是非常耗内存以及性能的
@synchronized底层封装了是一把递归锁,可以自动进行加锁解锁,这也是大家喜欢使用它的原因
@synchronized中lockCount控制递归,而threadCount控制多线程
@synchronized加锁的对象尽量不要为nil,不然起不到加锁的效果
总结
锁的世界才开始,今天探究了@synchronized递归锁。下面会探究其它类型的锁,让我们一起在锁的世界遨游。
文章由作者:嘿嘿小开发 逻辑iOS学员提供