@Synchronized的性能問題和安全性

將如下代碼clang查看一下

 self.lockA = [[NSObject alloc] init];
@synchronized(self.lockA) {
    NSLog(@"lockA");
};

得到如下結(jié)果:@Synchronized會變成一對基于try-catch的objc_sync_enter和objc_sync_exit的代碼

((void (*)(id, SEL, NSObject *))(void *)objc_msgSend)((id)self, sel_registerName("setLockA:"), ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        { id _rethrow = 0; id _sync_obj = (id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("lockA")); 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);

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_blmkswp957j3hbcpmjdtsvhw0000gn_T_TestSynchronized_ae1d39_mi_0);
        } 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內(nèi)部做了什么剃允。通過源碼我們可以找到:


    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed");
    
        result = recursive_mutex_lock(&data->mutex);
        require_noerr_string(result, done, "mutex_lock failed");
    } 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();
    }

done: 
    return result;

可以看到锭吨,源碼中現(xiàn)將obj轉(zhuǎn)換成SyncData楞遏,然后開啟了SyncData的mutex鎖。并且@synchronized(nil)不起任何作用凰锡,SyncData結(jié)構(gòu)體如下:

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;

? mutex秉继,一把遞歸鎖,這也是為什么我們可以在@Synchronized里面嵌套@Synchronized的原因盗棵。
? DisguisedPtr壮韭,這里DisguisedPtr其實就是對裸對象指針objc_object的一層包裝改寫。用于對象釋放后指向的指針纹因。

通過這邊文章http://satanwoo.github.io/2019/01/01/Synchronized/可知喷屋,id2data函數(shù)內(nèi)部實現(xiàn)會生成一個SyncList鏈表來管理SyncData,每一次都在多線程中執(zhí)行如下代碼:

- (void)test
{
    @synchronized (self.testArray) {
        self.testArray = @[].mutableCopy;
    }
}

testArray每次都會被賦予不同的值瞭恰。例如當(dāng)前節(jié)點為SyncDataO袱巨,A線程現(xiàn)在生成新的SyncDataA節(jié)點(同時B線程在等待A修改完畢并解鎖)济赎,當(dāng)新的節(jié)點產(chǎn)生后筹燕,下一個B線程執(zhí)行SyncDataO節(jié)點上的處理(因為SyncDataA生成過程中栓票,該線程一直在等待,但是此時后者已經(jīng)獲取到了SyncDataO)颜启,那么當(dāng)再次進來一個新的線程C執(zhí)行新的更改時偷俭,獲取到的SyncDataC與SyncDataO不是同個節(jié)點,那么也就不是同一個鎖农曲,這時這兩者可以同時運行社搅。參考setter內(nèi)部代碼實現(xiàn):(代碼來自http://satanwoo.github.io/2019/01/01/Synchronized/

static inline void reallySetProperty(id self, SEL _cmd, id newValue, 
  ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 
{
    id oldValue;
    // 計算結(jié)構(gòu)體中的偏移量
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:NULL];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:NULL];
    } else {
        // 某些程度的優(yōu)化
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    // 危險區(qū)
    if (!atomic) {
         // 第一步
        oldValue = *slot;

        // 第二步
        *slot = newValue;
    } else {
        spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
        _spin_lock(slotlock);
        oldValue = *slot;
        *slot = newValue;        
        _spin_unlock(slotlock);
    }

    objc_release(oldValue);
}

可知,當(dāng)setter完成后乳规,我們會釋放oldValue形葬,但是
oldValue = *slot; id *slot = (id*) ((char*)self + offset);
也就是說*slot的值有可能都是SyncDataK的值,那么就有可能出現(xiàn)objc_release的可能是同一塊內(nèi)存暮的,也就會出現(xiàn)double free導(dǎo)致崩潰笙以。

總結(jié):對于@Synchronized(obj)的使用,盡量保證傳入的obj對象不會再鎖中被更改冻辩,同時多個不同的更改應(yīng)該建立不同的obj猖腕。例如

@Synchronized(lockA){
self.A = xxx;
};
@Synchronized(lockB){
self.B = xxx;
};

其本身的性能問題在目前來看拆祈,并不是我們應(yīng)該優(yōu)先考慮的點,相比直接用NSLock倘感,還是@Synchronized略快放坏。所以我們在使用過程中,盡量避免在@Synchronized代碼塊中進行耗時操作即可老玛,

文章參考:
http://www.cocoachina.com/ios/20161205/18279.html
http://satanwoo.github.io/2019/01/01/Synchronized/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淤年,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜡豹,更是在濱河造成了極大的恐慌麸粮,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜廉,死亡現(xiàn)場離奇詭異弄诲,居然都是意外死亡,警方通過查閱死者的電腦和手機娇唯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門齐遵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人视乐,你說我怎么就攤上這事洛搀「易拢” “怎么了佑淀?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彰檬。 經(jīng)常有香客問我伸刃,道長,這世上最難降的妖魔是什么逢倍? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任捧颅,我火速辦了婚禮,結(jié)果婚禮上较雕,老公的妹妹穿的比我還像新娘碉哑。我一直安慰自己,他們只是感情好亮蒋,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布扣典。 她就那樣靜靜地躺著,像睡著了一般慎玖。 火紅的嫁衣襯著肌膚如雪贮尖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天趁怔,我揣著相機與錄音湿硝,去河邊找鬼薪前。 笑死,一個胖子當(dāng)著我的面吹牛关斜,可吹牛的內(nèi)容都是我干的示括。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼痢畜,長吁一口氣:“原來是場噩夢啊……” “哼例诀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起裁着,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤繁涂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后二驰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扔罪,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年桶雀,在試婚紗的時候發(fā)現(xiàn)自己被綠了矿酵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡矗积,死狀恐怖全肮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棘捣,我是刑警寧澤辜腺,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站乍恐,受9級特大地震影響评疗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茵烈,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一百匆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呜投,春花似錦加匈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至释漆,卻和暖如春悲没,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工示姿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甜橱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓栈戳,卻偏偏與公主長得像岂傲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子子檀,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • @synchronized 是遞歸鎖镊掖,類似NSRecursiveLock,遞歸調(diào)用不會引起死鎖,而NSLock是非...
    RunningTeemo閱讀 434評論 0 0
  • 作者:Ryan Kaplan 譯者:徐嘉宏原文地址:More than you want to know abou...
    簞食豆羹閱讀 10,652評論 13 100
  • 如果你已經(jīng)使用 Objective-C 編寫過任何并發(fā)程序褂痰,那么想必是見過 @synchronized 這貨了亩进。@...
  • 原文,此文只為總結(jié)學(xué)習(xí) 因為原文一些內(nèi)容寫的不太準(zhǔn)確缩歪,我按照我的理解做出了批注和補充归薛。 如果你已經(jīng)使用 Objec...
    lltree閱讀 2,922評論 2 11
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,109評論 1 32