在iOS開(kāi)發(fā)中會(huì)遇到多個(gè)線程要執(zhí)行同一份代碼的情況,比如同時(shí)修改統(tǒng)一個(gè)數(shù)據(jù)(火車(chē)賣(mài)票),通俗一點(diǎn)說(shuō)就是搶占資源的問(wèn)題。
通常要使用加鎖來(lái)實(shí)現(xiàn)這種同步機(jī)制,在GCD出現(xiàn)之前,一共有兩種方法。
1、同步塊(synchroization block)
-(void)sunchronizedMethod{
@synchronized(self) {
//安全代碼
}
}
這樣寫(xiě)會(huì)自動(dòng)創(chuàng)建一個(gè)鎖,等塊中代碼都執(zhí)行完畢,執(zhí)行到這段代碼結(jié)尾,鎖就釋放了。這樣寫(xiě)通常沒(méi)錯(cuò),因?yàn)樗梢员WC每個(gè)self持有的對(duì)象都能不受干擾的運(yùn)行其sunchronizedMethod方法,然而,濫用@synchronized(self)會(huì)降低代碼效率,因?yàn)楣灿猛粋€(gè)鎖的那些同步塊(synchroization block),都必須按順序執(zhí)行,若是在self對(duì)象上頻繁加鎖,那么程序可能要等另一段與此無(wú)關(guān)的代碼執(zhí)行完畢,才能繼續(xù)執(zhí)行當(dāng)前代碼,其實(shí)沒(méi)有這樣做的必要。
2、NSLock
_lock = [NSLock alloc]init];
-(void)sunchronizedMethod{
[_lock lock];
//安全代碼
[_lock unlock];
}
這兩種方法都很好,不過(guò)也有缺陷,比方說(shuō),在極端的情況下,同步塊(synchroization block)會(huì)導(dǎo)致死鎖,另外,其效率也不見(jiàn)得很高,而如果用NSLock的話,一旦遇到死鎖,就會(huì)非常麻煩。
最好的方案就是使用GCD,它能簡(jiǎn)單高效的方式給代碼加鎖。下面通過(guò)get和set方法來(lái)舉例說(shuō)明:當(dāng)我們操作一個(gè)對(duì)象的時(shí)候,假設(shè)將屬性設(shè)為atomic,那么實(shí)現(xiàn)的效果類似于以下代碼:
-(NSString *)someString{
@synchronized(self){
return _someString;
}
}
-(void)setSomeString:(NSString *)someString{
@synchronized(self){
_someString = someString;
}
}
剛才說(shuō)過(guò)濫用@synchronized(self)很危險(xiǎn),因?yàn)樗型綁K(synchroization block)都會(huì)彼此搶奪同一個(gè)鎖。要是有很多屬性都這么寫(xiě)的話,那么每個(gè)屬性的同步塊(synchroization block)都要等其他所有同步塊(synchroization block)執(zhí)行完畢才能執(zhí)行,這也許并不是開(kāi)發(fā)者想要的效果。我們只想令每個(gè)屬性各自獨(dú)立的同步。這么做雖然能夠提供某種程度的線程安全,但卻無(wú)法保證訪問(wèn)該對(duì)象時(shí)絕對(duì)是線程安全的。當(dāng)然,當(dāng)訪問(wèn)屬性的操作是確實(shí)是原子性的(atomic)。使用屬性時(shí),必定能從中獲取到有效值,然而在同一線程上多次調(diào)用get方法,每次的結(jié)果卻未必相同,因?yàn)樵趦纱蝕et方法之間,其他線程可能會(huì)將新的值寫(xiě)入屬性。
有一種簡(jiǎn)單高效的方法可以代替同步塊(synchroization block)和NSLock對(duì)象,那就是使用“串行同步隊(duì)列”。將讀取和寫(xiě)入操作都安排到同一個(gè)隊(duì)列中,即可保證數(shù)據(jù)同步。用法如下:
//NULL相當(dāng)于DISPATCH_QUEUE_SERIAL
_syncQueue = dispatch_queue_create("www.nnn.com", NULL);
-(NSString *)someString{
__block NSString *str;
dispatch_sync(_syncQueue, ^{
str = _someString;
});
return str;
}
-(void)setSomeString:(NSString *)someString{
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
這種實(shí)現(xiàn)的思路是:把set操作與get操作都安排到序列化的隊(duì)列里執(zhí)行,這樣的話,所有針對(duì)屬性的訪問(wèn)操作就同步了。全部加鎖任務(wù)都在GCD重處理,而GCD是在相當(dāng)深的底層來(lái)實(shí)現(xiàn)的,于是能夠做許多優(yōu)化,因此開(kāi)發(fā)者無(wú)須擔(dān)心那些事,只要專心把訪問(wèn)方法寫(xiě)好就行。
然而還可以進(jìn)一步優(yōu)化,set方法不一定非得是同步的。設(shè)置實(shí)例變量所用的方法,并不需要返回什么值,也就是實(shí)說(shuō),可以改成下面那樣:
-(void)setSomeString:(NSString *)someString{
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
這次只是把同步派發(fā)改成了異步派發(fā),從調(diào)用者的角度來(lái)看,這個(gè)小改動(dòng)可以提升set方法的執(zhí)行速度,而get方法依然會(huì)按照順序執(zhí)行。但這么做有個(gè)壞處,有可能程序會(huì)比較慢,因?yàn)閳?zhí)行異步派發(fā)的時(shí)候,需要拷貝塊,若拷貝塊所用的時(shí)間明顯超過(guò)執(zhí)行塊所花的時(shí)間,則這種做法比原來(lái)更慢。然而,若是派發(fā)給隊(duì)列的塊需要執(zhí)行更為繁重的任務(wù),那么仍然可以考慮這種方案。
多個(gè)獲取方法可以并發(fā)執(zhí)行,而set方法和get方法之間不能并發(fā)執(zhí)行,利用這個(gè)特點(diǎn),還能寫(xiě)出更快的代碼來(lái),我們這次改用并發(fā)隊(duì)列:
_syncQueue = dispatch_queue_create("www.nnn.com", DISPATCH_QUEUE_CONCURRENT);
-(NSString *)someString{
__block NSString *str;
dispatch_sync(_syncQueue, ^{
str = _someString;
});
return str;
}
-(void)setSomeString:(NSString *)someString{
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
像這樣寫(xiě)代碼,還無(wú)法實(shí)現(xiàn)同步。所有g(shù)et操作和set操作都會(huì)在同一個(gè)隊(duì)列上執(zhí)行,不過(guò)由于是并發(fā)隊(duì)列,所以set和get操作可以隨時(shí)執(zhí)行,而這是我們恰恰不想看到的。此問(wèn)題用一個(gè)簡(jiǎn)單的GCD功能就能解決,他就是柵欄(barrier)。下面的函數(shù)可以向隊(duì)列中派發(fā)塊,將其作為柵欄(barrier)使用:
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
在隊(duì)列中,柵欄塊必須單獨(dú)執(zhí)行,不能與其他塊并行。這只對(duì)并發(fā)隊(duì)列有意義,因?yàn)榇斜緛?lái)就是順序逐個(gè)執(zhí)行的。并發(fā)隊(duì)列如果發(fā)現(xiàn)接下來(lái)要處理的塊是個(gè)柵欄塊(barrier block),那么久一直要等到當(dāng)前所有并發(fā)塊都執(zhí)行完畢,才會(huì)單獨(dú)執(zhí)行這個(gè)柵欄塊,待柵欄塊執(zhí)行過(guò)后,再按正常方式繼續(xù)鄉(xiāng)下處理。這也就是說(shuō),當(dāng)我們進(jìn)行set的時(shí)候,這個(gè)塊是單獨(dú)執(zhí)行的,這樣就不會(huì)出現(xiàn)多個(gè)線程同時(shí)寫(xiě)入數(shù)據(jù)的情況。
代碼如下:
_syncQueue = dispatch_queue_create("www.nnn.com", DISPATCH_QUEUE_CONCURRENT);
-(NSString *)someString{
__block NSString *str;
dispatch_sync(_syncQueue, ^{
str = _someString;
});
return str;
}
-(void)setSomeString:(NSString *)someString{
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}