iOS线程安全最常用的锁 – @synchronized

前篇文章整理了iOS中常见的几种锁,我最常用的是@synchronized,接下来咱们来一同学习下其底层原理

@synchronized是怎数据结构严蔚敏么完结递归互斥的?是怎么源码编辑器完结可重入的呢?源码编辑器带着这两个问题去剖析源码。

咱们先用clang命令检查@synchronize源码中的图片d的.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_E链表排序XIT数据结构知识点总结的结构函数和析构函数,结构函数什么都没做,析构函数出了效果域就会调用的objc_approachsync_exit@synchronizios15ed会被编译成objc_sync_enterobjc_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_enterobjc_sync链表的创建_exit都是先判定参数objnil就啥也不做,obj不为nil经过id2data获得一个SyncData的数据结构,而且经过id2data的第二个参数来区分是enter调用的id2data还是exit调用的id2dataSyncData里拿出递归锁加锁解锁操作。

先了解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分配了一把递归锁和记载线程数量。这两个分配记载就是多线程下递归调用的底子。(@synchronized(objc1)相当于是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数据结构教程第5版李春葆答案_mute源码网站x_t的封装。

关于appstore这个锁能够持续看其定义:

咱们现在要关注的是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快速缓存 查找

行数许多,能够分为几链表个部分,第一部分快速缓存查找:appreciate

1.TLS快速缓存中只存储了一个SyncDa源码中的图片ta数据,从这链表逆置儿取出的SyncDataobject@synchronized 的参数object做比照(相同则阐明是咱们要找到的SyncData
2.假如找链表c语言到了SyncData,对lockCountthreadCount做记载更新,直接把SyncData回来出去;
3.假如没有找到SyncData,则ios下载进入下一部分。

注意⚠️TL源码交易平台S(thread Local Store)为线程本地存储。也就是说每条线程都会有一个这样的FastCa链表数据结构che。并不是整个过程只要一个FastCache。假如在FastCache找到就直接回来。

syncCache 查找

1.遍历带锁的每个线程的缓存,取出每一个SyncCacheItem,取出Sy数据结构严蔚敏ncCacheItem里边的SyncDataSyncDataobject@synchronized 的参数objios应用商店ect做比源码交易平台照(相同链表和数组的区别则阐明是咱们要找到的SyncData
2.假如找到了SyncData,对lockCountthreadCount做记载更新,直接APPSyncData回来出去;
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;

同样是在缓存找,因为源码编辑器Syios系统ncCache里边是数组,这儿遍历查找。能够看其链表的创建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; 
}

其间_o链表的创建bjc_pthread_approachdata结构如下:

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类型,Strios16ipedMap存储的是 真机下8张表/模拟器下64张表,每张表里存储的是许多的SyncList = SyncData单向链表 + lock。这儿也阐明晰源码一个问题,为什么@siOSynchronized在模拟器中的性能会很差,因为在模拟链表数据结构器中会从64张表中去查找锁,而真机是从8张表中查找锁.

1.遍历大局HashStripedMap,取出的SyncData单向链表object@synchronized 的参数object做比照(相同则阐明ios16是咱们要找到的SyncappointmentData源码精灵永久兑换码),假如比照不是同一个,会找链表下一个元素比照。
2.当前没有与object相关的SyncData,则直接回来nil
3.找到一个没用过的SyncData,就对其缓存到TLS快速缓存线程缓存,并回来这个SyncDa数据结构知识点总结ta 4.假如是TLS快速缓存线程缓ios是苹果还是安卓大局Hash表StripedMap都没有找到,阐明object被第一次加源码网站锁,去创立一个SyncData回来它。

缓存到线程中

在sDataLists 查找 及 创立SyncData后会调用done,这儿只会在Enter的时分履行,假如支撑快速缓存而且快速缓存里边没有值,那么在快速缓存里边去添加,便利下次递归的时分来加锁。不然ios系统就在线程iOS缓存里边添加。

id2da源码编辑器下载ta在找锁的过程中运用了类似三级缓存的流程,这样的目的是为了在多线程中办理锁,而且让线程以最快的速度拿到锁,来完结加锁解锁的操作,然后提升功率。appetite

总结

  • 在快速缓存中没有找到
  • 在线程缓存中也没有找到
  • 在大局的sDataLists中也没有找到
  • 那就新建一个SyncData

iOS线程安全最常用的锁 - @synchronized

发表评论

提供最优质的资源集合

立即查看 了解详情