下面代碼會發(fā)生什么問題聚磺?
@property (nonatomic, strong) NSString *target;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}
當(dāng)時我把自定義的隊列看成了串行隊列坯台,然后回答:“沒錯呀”。后來一運行崩潰了……
面試后咧最,我就仔細回想捂人,敲了Demo,看看崩潰原因是啥矢沿。
正好試試小伙伴給我介紹的調(diào)試野指針的方法滥搭,XCode7以上才有的Address Sanitizer。
打開后發(fā)現(xiàn)是經(jīng)典的EXC_BAD_ACCESS錯誤捣鲸,以我淺薄的經(jīng)驗來看瑟匆,這種一般是對一個已釋放的內(nèi)存的對象再次發(fā)送消息出現(xiàn)的。
1503650160314466.jpg
噢栽惶,看來是對已釋放的對象再次發(fā)送了release信息愁溜。
我又留意到,這個對象是Strong修飾的外厂。
那么他的Setter方法在MRC上就相當(dāng)于
- (void)setTarget:(NSString *)target { [target retain];//先保留新值 [_target release];//再釋放舊值 _target = target;//再進行賦值}
那么什么時候會導(dǎo)致過多調(diào)用release呢冕象,因為這是個并行隊列+異步男旗。
那么假如隊列A執(zhí)行到步奏2香罐,還沒到步驟3時疲酌,隊列B也執(zhí)行到步驟2刑巧,那么這個對象就會被過度釋放嗜闻,導(dǎo)致向已釋放內(nèi)存對象發(fā)送消息而崩潰仍翰。
后來我想怎么可以修改這段代碼變?yōu)椴槐罎⒌哪兀?br> 1.使用串行隊列
將set方法改成在串行隊列中執(zhí)行就行钧嘶,這樣即使異步躁倒,但所有block操作追加在隊列最后依次執(zhí)行幔亥。
2. 使用atomic
atomic關(guān)鍵字相當(dāng)于在setter方法加鎖耻讽,這樣每次執(zhí)行setter都是線程安全的,但這只是單獨針對setter方法而言的狹義的線程安全帕棉。
3.使用weak關(guān)鍵字
weak的setter沒有保留新值或者保留舊值的操作针肥,所以不會引發(fā)重復(fù)釋放。當(dāng)然這個時候要看具體情況能否使用weak笤昨,可能值并不是所需要的值祖驱。
4.使用Tagged Pointer
Tagged Pointer是蘋果在64位系統(tǒng)引入的內(nèi)存技術(shù)。簡單來說就是對于NSString(內(nèi)存小于60位的字符串)或NSNumber(小于2^31)瞒窒,64位的指針有8個字節(jié)捺僻,完全可以直接用這個空間來直接表示值,這樣的話其實會將NSString和NSNumber對象由一個指針轉(zhuǎn)換成一個值類型,而值類型的setter和getter又是原子的匕坯,從而線程安全束昵。
比如上述代碼的字符串改短一些,就不會崩潰了葛峻。
從而我們可以總結(jié)到锹雏,線程安全有以下幾種方法:
單線程串行訪問
訪問加鎖
使用不進行額外操作的關(guān)鍵字(weak)
使用值類型
然而這只是保證了基本的線程安全(不崩潰),若是需要保證訪問出符合預(yù)期的數(shù)據(jù)术奖,則需要采用GCD的barrier或者自己在合適的時機加鎖礁遵。