iOSスレッドセーフのために最も一般的に使用されるロック-@synchronized

前回の記事では、iOSで一般的なロックをいくつか整理しました。最も一般的に使用されるものは、私が使用するものです@synchronized。次に、基本的な原則を一緒に学びましょう。

@synchronized再帰的な相互排除を実現するにはどうすればよいですか?再入可能性を達成する方法は?これらの2つの質問に答えて、ソースコードを分析します。

最初にclangコマンドを使用して@synchronized、.cppの実装を表示します

#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *syObject = [NSObject alloc];
        @synchronized (syObject) {
        }
    }
    return NSApplicationMain(argc, argv);
}
NSObject *syObject = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
{ 
    id _rethrow = 0; 
    id _sync_obj = (id)syObject;
    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);}
}

コンパイルされたコードが分析され、最初にコンストラクタとデストラクタが呼び出され、objc_sync_enter次にコンストラクタとデストラクタが呼び出され_SYNC_EXITます。コンストラクタは何もせず、スコープ外になるとデストラクタが呼び出されます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;
}

objc_sync_exit

// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
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;
}

objc_sync_enterまた、パラメータが最初に決定objc_sync_exitされた場合データ構造を取得し、2番目のパラメータを渡して、呼び出された呼び出されたかを区別するのではobjなく、どちらnilも何もしません再帰ロック、ロック、ロック解除操作を解除します。objnilid2dataSyncDataid2dataenterid2dataexitid2dataSyncData

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;

これは単一リンクリスト構造であり、再帰ロックと記録されたスレッドの数がSyncDataそれぞれに割り当てられていることがわかります。@synchronized 的参数objectこれらの2つの割り当てレコードは、マルチスレッドでの再帰呼び出しの基礎です。@synchronized(objc1) yesと同等SyncData->object=objc1

using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
class recursive_mutex_tt : nocopy_t {
    os_unfair_recursive_lock mLock;
    ......
}

OS_UNFAIR_RECURSIVE_LOCK_AVAILABILITY
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_lockはカプセル化に基づいています。pthread_mutex_t以前のバージョンでは、これはパッケージに基づいていました。

このロックに関しては、引き続きその定義を確認できます。

我们现在要关注的是SyncData里的成员是如何在多线程下实现递归调用的。关键逻辑还得看id2data里面做了什么。

static SyncData* id2data(id object, enum usage why) {
    // 从全局hash表中, 通过object获取锁
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    // 从全局hash表中, 通过object获取指向SyncData单向链表的头指针
    SyncData **listp = &LIST_FOR_OBJ(object);
    // 查询后需要返回的结果
    SyncData* result = NULL;

#if SUPPORT_DIRECT_THREAD_KEYS
    /* 快缓存。 : 
    2个固定的线程键 储存一个单独的 SyncCacheItem 
    Fast cache: two fixed pthread keys store a single SyncCacheItem. 
    这就避免了对于一次只同步单个对象的线程使用SyncCache的malloc 
    This avoids malloc of the SyncCache for threads that only synchronize a single object at a time. 
    SYNC_DATA_DIRECT_KEY == SyncCacheItem.data 
    SYNC_COUNT_DIRECT_KEY == SyncCacheItem.lockCount 
    */
    // Check per-thread single-entry fast cache for matching object
    bool fastCacheOccupied = NO;
    //拿出快速缓存里面的SyncData
    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");
            }
            //加锁的时候(ENTER)
            switch(why) {
            case ACQUIRE: {
                lockCount++;
                //lockCount放入快速缓存
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            //解锁的时候(EXIT)
            case RELEASE:
                lockCount--;
                //取出加锁的时候的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
                    // SyncData中记录线程数量的-1
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }
            //返回
            return result;
        }
    }
#endif

    /*
        SyncCache 查找
        在线程的TLS中找objc对象,然后再维护2个count lockCount 和 threadCount
        每个线程都只有一份
    */
    // 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--;
                //如果==0,该线程已经使用完了
                if (item->lockCount == 0) {
                    // 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;
        }
    }

    /*
        sDataLists 查找
        这里加锁内容包括sDataLists查找,和创建SyncData,目的是为了防止创建重复的SyncData
    */
    // 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) {
            //找到SyncData
            if ( p->object == object ) {
                result = p;
                // atomic because may collide with concurrent RELEASE
                // threadCount + 1
                OSAtomicIncrement32Barrier(&result->threadCount);
                // 跳转:done
                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;
        }
    }
    /*
        创建SyncData
    */
    // 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);
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }
    return result;
}

TLS快速缓存 查找

行数很多,可以分为几个部分,第一部分快速缓存查找:

1.TLS快速缓存中只存储了一个SyncData数据,从这里取出的SyncDataobject@synchronized 的参数object做对比(相同则说明是我们要找到的SyncData
2.如果找到了SyncData,对lockCountthreadCount做记录更新,直接把SyncData返回出去;
3.如果没有找到SyncData,则进入下一部分。

注意⚠️TLS(thread Local Store)为线程本地存储。也就是说每条线程都会有一个这样的FastCache。并不是整个过程只有一个FastCache。如果在FastCache找到就直接返回。

syncCache 查找

1.遍历带锁的每个线程的缓存,取出每一个SyncCacheItem,取出SyncCacheItem里面的SyncDataSyncDataobject@synchronized 的参数object做对比(相同则说明是我们要找到的SyncData
2.如果找到了SyncData,对lockCountthreadCount做记录更新,直接把SyncData返回出去;
3.如果没有找到SyncData,则进入第三部分。

typedef struct {
    SyncData *data;
    unsigned int lockCount;  // number of times THIS THREAD locked this block
} SyncCacheItem;
typedef struct SyncCache {
    unsigned int allocated;
    unsigned int used;
    SyncCacheItem list[0];
} SyncCache;

同样是在缓存找,因为SyncCache里面是数组,这里遍历查找。可以看其中fetch_cache(NO)中的代码:

static SyncCache *fetch_cache(bool create) {
    _objc_pthread_data *data; 
    data = _objc_fetch_pthread_data(create); 
    if (!data) return NULL; 
    if (!data->syncCache) { 
        if (!create) { return NULL; } 
        else {
            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. 
    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_pthread_data结构如下:

typedef struct {
    struct _objc_initializing_classes *initializingClasses; // for +initialize
    struct SyncCache *syncCache;  // for @synchronize
    struct alt_handler_list *handlerList;  // for exception alt handlers
    char *printableNames[4];  // temporary demangled names for logging
    const char **classNameLookups;  // for objc_getClass() hooks
    unsigned classNameLookupsAllocated;
    unsigned classNameLookupsUsed;
    // If you add new fields here, don't forget to update 
    // _objc_pthread_destroyspecific()
} _objc_pthread_data;

这里也进一步说明了TLSsyncCache也是每个线程中都存在一份的。 很明显线程缓存保存了好多的SyncData+lockCount

sDataLists 查找 或 创建SyncData

如果在快速缓存和缓存里面都没有找到,这时候是这个线程第一次走到 @synchronized的地方,系统会去sDataLists里面去找对应的SyncData对象.sDataLists全局Hash表,在id2data函数一开头就获取了全局Hash表的元素

// Use multiple parallel lists to decrease contention among unrelated objects.
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

template<typename T>
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];
    ......
}

struct SyncList {
    SyncData *data;
    spinlock_t lock;
    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

sDataListsStripedMap类型,StripedMap存储的是 真机下8张表/模拟器下64张表,每张表里存储的是很多的SyncList = SyncData单向链表 + lock。这里也说明了一个问题,为什么@synchronized在模拟器中的性能会很差,因为在模拟器中会从64张表中去查找锁,而真机是从8张表中查找锁.

1.遍历全局HashStripedMap,取出的SyncData单向链表object@synchronized 的参数object做对比(相同则说明是我们要找到的SyncData),如果对比不是同一个,会找链表下一个元素对比。
2.当前没有与object关联的SyncData,则直接返回nil
3.找到一个没用过的SyncData,就对其缓存到TLS快速缓存线程缓存,并返回这个SyncData 4.如果是TLS快速缓存线程缓存全局Hash表StripedMap都没有找到,说明object被第一次加锁,去创建一个SyncData 返回它。

缓存到线程中

sDataListsでSyncDataを見つけて作成した後に呼び出されますdone。これは、キャッシュがサポートされている場合にのみEnter実行されます。キャッシュがサポートされていて、キャッシュに値がない場合は、キャッシュに追加して、ロックする次の再帰を容易にします。 。それ以外の場合は、スレッドキャッシュに追加されます。

id2dataロックを見つけるプロセスで使用されるプロセスは类似三级缓存、複数のスレッドのロックを管理し、スレッドが最速の速度でロックを取得できるようにして、ロックとロック解除の操作を完了し、それによって効率を向上させることです。

要約する

  • 高速キャッシュに見つかりません
  • スレッドキャッシュにも見つかりません
  • また、グローバルsDataListsにはありません
  • 次に、新しいSyncDataを作成します

WX20220707-094714@2x.png

おすすめ

転載: juejin.im/post/7117441321270247438
おすすめ