在多线程中,访问同一个资源,会有线程安全问题,我们通常会在内存进行存取的时候进行加锁:
@synchronized (self) {
// code.....
}
我们查看其汇编指令
0x108840e6e <+46>: callq 0x108841432 ; symbol stub for: objc_sync_enter
-> 0x108840e73 <+51>: leaq 0x220e(%rip), %rdi ; @"RRRRR"
0x108840e7a <+58>: xorl %ecx, %ecx
0x108840e7c <+60>: movb %cl, %dl
0x108840e7e <+62>: movl %eax, -0x34(%rbp)
0x108840e81 <+65>: movb %dl, %al
0x108840e83 <+67>: callq 0x1088413e4 ; symbol stub for: NSLog
0x108840e88 <+72>: jmp 0x108840e8d ; <+77> at ViewController.m
0x108840e8d <+77>: movq -0x28(%rbp), %rdi
0x108840e91 <+81>: callq 0x108841438 ; symbol stub for: objc_sync_exit
我们可以看出:@synchronized
关键字,转化为了objc_sync_enter
和 objc_sync_exit
两个函数调用。下面,我们着重来分析这两个函数。
objc_sync_enter
这里先直接贴码了:
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
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;
}
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
-
objc_sync_nil
: 如果传入的加锁对象为nil
,则不做任何处理。 - 如果对象不为空,就使用
递归锁(recursive_mutext_t)
进行加锁
-
SyncData
结构体是一个链表
,用来纪录有多少个递归锁重入。
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
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
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: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case 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);
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->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) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
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) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES); // 通过pthreadkey对线程进行绑定
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
id2data
该函数实现有些长,我们慢慢来看,
首先,通过
SYNC_DATA_DIRECT_KEY
获取当前线程的锁信息。如果有
SyncData
信息,通过SYNC_COUNT_DIRECT_KEY
,获取其lockCount
,lockCount
为锁了多少次。objc_sync_enter
调用时,会lockCount ++
。objc_sync_exit
调用时,会lockCount--
,1, 第一次进来没有锁,然后让 threadCount和lockCount 各自加一,这样,
threadCount = 1, lockCount = 1
存到tls
和缓存cache
中,方便下次查找。2,如果不是第一次进来,同一线程,对threadCount和lockCount 加一,存储到
tls
中,并返回。3,如果不是第一次进来,且不是同一线程,对全局的线程空间进行查找,对
threadCount
和lockCount
进行++
处理。
objc_sync_exit
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;
}
- 1,如果对象为空,则什么都不做。
- 2,如果对象不为空,同样调用
id2data
函数,只是这里的模式改成了RELEASE
。会根据pthreadKey
和SYNC_COUNT_DIRECT_KEY
找到该SyncData
,对threadCount--
和lockCount--
。
整个@synchronized
的结构如下图所示
@synchronized
是由哈希表组成,内部通过SyncList
来组装多线程
的情况,通过链表
结构来解决当前线程可重入的问题。通过tls(thread local storage)
来进行进行查找。