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

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

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

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

    static void _I_Demo_synchronizedTest(Demo * self, SEL _cmd) { 
    NSMutableArray *arr; 
    {
      id _sync_obj = (id)arr;
      objc_sync_enter(_sync_obj); // 同步鎖進入彩倚,參數(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);// 調用結構體的構造函數(shù)扶平,參數(shù)是arr 
          } catch (id e) { 
      } 
   }
}

進一步署恍,查看objc_sync_enter和objc_sync_exit的源碼實現(xiàn),如下:

        int objc_sync_enter(id obj)
    { 
              int result = OBJC_SYNC_SUCCESS;
              if (obj) {
            // 根據(jù)obj獲取對應的SyncData節(jié)點蜻直,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用的是遞歸鎖(即同個線程可重入,而不會導致死鎖)概而;
2呼巷、@synchronized(nil)是不上鎖的
接著看看如下關鍵的數(shù)據(jù)結構,顯然赎瑰,SyncList是個單鏈表王悍,SyncData是單鏈表節(jié)點,而整體存儲則是一個“拉鏈法哈希表”餐曼。

        typedef struct SyncData {
             struct SyncData* nextData; // 指向下一個SyncData節(jié)點的指針
             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:單鏈表

      // 根據(jù)obj獲取對應的SyncData節(jié)點static SyncData* id2data(id object, enum usage why)
      { 
          spinlock_t *lockp = &LOCK_FOR_OBJ(object); // SyncList鎖
         SyncData **listp = &LIST_FOR_OBJ(object); // obj對應的SyncData節(jié)點所在的
        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對應的SyncData節(jié)點 
                  result = p; 
                // SyncData節(jié)點對應的線程數(shù)加1  
                 OSAtomicIncrement32Barrier(&result->threadCount); 
                  goto done; 
        }
    // SyncData節(jié)點對應的遞歸鎖沒有線程在用了源譬,回收重用集惋,可以節(jié)省節(jié)點創(chuàng)建的時間和空間 
      if ( (firstUnused == NULL) && (p->threadCount == 0) ) 
                    firstUnused = p; 
            }
     // 鏈表中還沒有obj對應的SyncData節(jié)點,但是有可重用的SyncData節(jié)點
    // an unused one was found, use it
             if ( firstUnused != NULL ) {
                  result = firstUnused;
                  result->object = (objc_object *)object;
                  result->threadCount = 1;
                  goto done;
            }
        }
// 鏈表中還沒有obj對應的SyncData節(jié)點踩娘,而且沒有可重用的SyncData節(jié)點
       result = (SyncData*)calloc(sizeof(SyncData), 1);
       result->object = (objc_object *)object;
       result->threadCount = 1;
       new (&result->mutex) recursive_mutex_t();
// 新建的SyncData節(jié)點往鏈表頭部加 
       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的源碼實現(xiàn)刮刑,再回頭看看crash,問題主要有兩個:
1、arr沒有初始化時為nil雷绢,同步鎖沒生效泛烙,block并非臨界區(qū);
2翘紊、arr被修改了蔽氨,即內存地址并非常量,線程1拿到arr對應的地址為addr1帆疟,進入block鹉究;線程2拿到
arr對應的地址為addr2,同樣可以進入block鸯匹,而不會等待線程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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末泄伪,一起剝皮案震驚了整個濱河市殴蓬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蟋滴,老刑警劉巖染厅,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異津函,居然都是意外死亡肖粮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門尔苦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涩馆,“玉大人,你說我怎么就攤上這事允坚』昴牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵稠项,是天一觀的道長涯雅。 經常有香客問我,道長展运,這世上最難降的妖魔是什么活逆? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮拗胜,結果婚禮上蔗候,老公的妹妹穿的比我還像新娘。我一直安慰自己埂软,他們只是感情好琴庵,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般迷殿。 火紅的嫁衣襯著肌膚如雪儿礼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天庆寺,我揣著相機與錄音蚊夫,去河邊找鬼。 笑死懦尝,一個胖子當著我的面吹牛知纷,可吹牛的內容都是我干的。 我是一名探鬼主播陵霉,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼琅轧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踊挠?” 一聲冷哼從身側響起乍桂,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎效床,沒想到半個月后睹酌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡剩檀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年憋沿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪猴。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡辐啄,死狀恐怖,靈堂內的尸體忽然破棺而出运嗜,到底是詐尸還是另有隱情则披,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布洗出,位于F島的核電站士复,受9級特大地震影響,放射性物質發(fā)生泄漏翩活。R本人自食惡果不足惜阱洪,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菠镇。 院中可真熱鬧冗荸,春花似錦、人聲如沸利耍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至程癌,卻和暖如春舷嗡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嵌莉。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工进萄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锐峭。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓中鼠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沿癞。 傳聞我的和親對象是個殘疾皇子援雇,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容