mac開發(fā)系列31:線程同步鎖@synchronized源碼理解

今天遇到一枚crash,利用堆棧,初步判斷原因是“多線程寫DB”,問題代碼大致如下:

        NSMutableArray *arr;
        @synchronized(arr) { 
        arr = [self func]; // func方法中有寫DB操作  
        if(arr == nil) { 
            arr = [NSMutableArray array]; 
        }
    }

可是這里明明用了同步鎖@synchronized,為什么還會(huì)有多個(gè)線程同時(shí)進(jìn)入block呢?老套路,重寫得到如下C++實(shí)現(xiàn):

    static void _I_Demo_synchronizedTest(Demo * self, SEL _cmd) { 
    NSMutableArray *arr; 
    {
      id _sync_obj = (id)arr;
      objc_sync_enter(_sync_obj); // 同步鎖進(jìn)入,參數(shù)是arr 
        try { 
            struct _SYNC_EXIT {
                _SYNC_EXIT(id arg) : sync_exit(arg) {}  
                ~_SYNC_EXIT() {objc_sync_exit(sync_exit); // 同步鎖退出,參數(shù)是arr 
  } 
            id sync_exit;
        }   _sync_exit(_sync_obj);// 調(diào)用結(jié)構(gòu)體的構(gòu)造函數(shù),參數(shù)是arr 
          } catch (id e) { 
      } 
   }
}

進(jìn)一步,查看objc_sync_enter和objc_sync_exit的源碼實(shí)現(xiàn),如下:

        int objc_sync_enter(id obj)
    { 
              int result = OBJC_SYNC_SUCCESS;
              if (obj) {
            // 根據(jù)obj獲取對(duì)應(yīng)的SyncData節(jié)點(diǎn),id2data函數(shù)在下面有解析
            SyncData* data = id2data(obj, ACQUIRE);// 上鎖 
            result = recursive_mutex_lock(&data->mutex); } 
        else 
          { // @synchronized(nil) does nothing 
    }
       return result;
    }

以下:

               int objc_sync_exit(id obj)
                {   int result = OBJC_SYNC_SUCCESS;
                    if (obj) { 
                        SyncData* data = id2data(obj, RELEASE); // 釋放鎖 
                        result = recursive_mutex_unlock(&data->mutex); 
                    } else {
               // @synchronized(nil) does nothing 
                  } 
                  return result;
            } 

從上面源碼可以看出:
1、@synchronized用的是遞歸鎖(即同個(gè)線程可重入,而不會(huì)導(dǎo)致死鎖);
2、@synchronized(nil)是不上鎖的
接著看看如下關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),顯然,SyncList是個(gè)單鏈表,SyncData是單鏈表節(jié)點(diǎn),而整體存儲(chǔ)則是一個(gè)“拉鏈法哈希表”。

        typedef struct SyncData {
             struct SyncData* nextData; // 指向下一個(gè)SyncData節(jié)點(diǎn)的指針
             DisguisedPtr<objc_object> object; // @synchronized的參數(shù)obj
             int32_t threadCount; // number of THREADS using this block   
             recursive_mutex_t mutex; // 遞歸鎖
          } SyncData;

        struct SyncList {
               SyncData *data; // 單鏈表頭指針 
               spinlock_t lock; // 保證多線程安全訪問該鏈表 
               SyncList() : data(nil) { }
        };

define LOCK_FOR_OBJ(obj) sDataLists[obj].lock

define LIST_FOR_OBJ(obj) sDataLists[obj].data

  static StripedMap<SyncList> sDataLists; // 哈希表,key:obj,value:?jiǎn)捂湵?
      // 根據(jù)obj獲取對(duì)應(yīng)的SyncData節(jié)點(diǎn)static SyncData* id2data(id object, enum usage why)
      { 
          spinlock_t *lockp = &LOCK_FOR_OBJ(object); // SyncList鎖
         SyncData **listp = &LIST_FOR_OBJ(object); // obj對(duì)應(yīng)的SyncData節(jié)點(diǎn)所在的
        SyncList SyncData* result = NULL;// 這里省略一大坨cache代碼 

        lockp->lock(); 
        {
            SyncData* p; 
            SyncData* firstUnused = NULL;
       // 遍歷單鏈表 
            for (p = *listp; p != NULL; p = p->nextData) { 
                  if ( p->object == object ) {
              // 找到obj對(duì)應(yīng)的SyncData節(jié)點(diǎn) 
                  result = p; 
                // SyncData節(jié)點(diǎn)對(duì)應(yīng)的線程數(shù)加1  
                 OSAtomicIncrement32Barrier(&result->threadCount); 
                  goto done; 
        }
    // SyncData節(jié)點(diǎn)對(duì)應(yīng)的遞歸鎖沒有線程在用了,回收重用,可以節(jié)省節(jié)點(diǎn)創(chuàng)建的時(shí)間和空間 
      if ( (firstUnused == NULL) && (p->threadCount == 0) ) 
                    firstUnused = p; 
            }
     // 鏈表中還沒有obj對(duì)應(yīng)的SyncData節(jié)點(diǎn),但是有可重用的SyncData節(jié)點(diǎn)
    // an unused one was found, use it
             if ( firstUnused != NULL ) {
                  result = firstUnused;
                  result->object = (objc_object *)object;
                  result->threadCount = 1;
                  goto done;
            }
        }
// 鏈表中還沒有obj對(duì)應(yīng)的SyncData節(jié)點(diǎn),而且沒有可重用的SyncData節(jié)點(diǎn)
       result = (SyncData*)calloc(sizeof(SyncData), 1);
       result->object = (objc_object *)object;
       result->threadCount = 1;
       new (&result->mutex) recursive_mutex_t();
// 新建的SyncData節(jié)點(diǎn)往鏈表頭部加 
       result->nextData = *listp;
       *listp = result;
 done:
       lockp->unlock();
       return result;}

}

    template<typename T>
    class StripedMap {
    #if TARGET_OS_EMBEDDED 
        enum { StripeCount = 8 };
      #else
        enum { StripeCount = 64 };#endif 
        static unsigned int indexForPointer(const void *p) {
        // 取obj地址的哈希值作為數(shù)組的index 
          uintptr_t addr = reinterpret_cast<uintptr_t>(p);
          return ((addr >> 4) ^ (addr >> 9)) % StripeCount; 
}
     public: 
      T& operator[] (const void *p) { 
            return array[indexForPointer(p)].value; 
      }
  };

搞清楚了@synchronized的源碼實(shí)現(xiàn),再回頭看看crash,問題主要有兩個(gè):
1、arr沒有初始化時(shí)為nil,同步鎖沒生效,block并非臨界區(qū);
2、arr被修改了,即內(nèi)存地址并非常量,線程1拿到arr對(duì)應(yīng)的地址為addr1,進(jìn)入block;線程2拿到
arr對(duì)應(yīng)的地址為addr2,同樣可以進(jìn)入block,而不會(huì)等待線程1執(zhí)行完block。

參考鏈接:
https://opensource.apple.com/source/objc4/objc4-680/runtime/objc-sync.mm
https://github.com/opensource-apple/objc4/blob/master/runtime/objc-private.h

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容