多線程方案:

比較常用的是GCD,是直接用Block去寫代碼的。使代碼比較緊湊。
GCD常用函數(shù)
同步執(zhí)行任務(wù):dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
異步執(zhí)行任務(wù):dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
控制任務(wù)的執(zhí)行順序:
dispatch_barrier_async:柵欄函數(shù),它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行之后,barrier函數(shù)之后的操作才會得到執(zhí)行。并且需要在異步并發(fā)隊(duì)列才有效。
dipatch_group:線程組,可以實(shí)現(xiàn)A、B、C、任務(wù)并發(fā)執(zhí)行,完了之后再通知執(zhí)行D任務(wù)。
各種多線程隊(duì)列的執(zhí)行效果:

注意:全局隊(duì)列也是一個并發(fā)隊(duì)列,主隊(duì)列是一個串行隊(duì)列。同步和異步主要影響能不能開啟新線程
多線程的安全隱患:當(dāng)多個線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。
面試題
一
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
以上代碼在主線程中執(zhí)行會產(chǎn)生死鎖。dispatch_sync 會立馬在當(dāng)前線程執(zhí)行任務(wù)
二
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"執(zhí)行任務(wù)2");
});
NSLog(@"執(zhí)行任務(wù)3");
以上代碼在主線程執(zhí)行不會產(chǎn)生死鎖。dispatch_async并不會立馬執(zhí)行。打印順序是任務(wù)1、任務(wù)3、任務(wù)2。
三
NSLog(@"執(zhí)行任務(wù)1");
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{//block01
NSLog(@"執(zhí)行任務(wù)2");
dispatch_sync(queue, ^{//block02
NSLog(@"執(zhí)行任務(wù)3");
});
NSLog(@"執(zhí)行任務(wù)4");
});
NSLog(@"執(zhí)行任務(wù)5");
以上代碼在主線程執(zhí)行會產(chǎn)生死鎖。會生成一個新線程,這個新線程是串行執(zhí)行任務(wù),先執(zhí)行block01,然后再執(zhí)行block02,由于block01在前面,要執(zhí)行完block01才能執(zhí)行block02,而block02又必須要等block01執(zhí)行完,這樣就互相等待,產(chǎn)生死鎖。
四
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"3");
});
- (void)test{
NSLog(@"2");
}
以上代碼只打印了1和3。因?yàn)閜erformSelector:withObject:afterDelay方法的本質(zhì)是往RunLoop里面添加了一個定時(shí)器。由于在這個子線程中沒有啟動RunLoop,所以test方法并不會執(zhí)行。凡是有afterDelay的方法都是在RunLoop實(shí)現(xiàn)的。如果沒有afterDelay比如performSelector:withObject:方法則是通過objc_msgSend執(zhí)行。
死鎖總結(jié)
使用sync同步執(zhí)行往當(dāng)前串行隊(duì)列中添加任務(wù),這樣就會產(chǎn)生死鎖
線程同步方案:鎖
1、OSSpinLock:(自旋鎖),等待鎖的線程會處于忙等狀態(tài),一直占用著CPU資源。
注意這個鎖目前是不安全的,會出現(xiàn)線程優(yōu)先級反轉(zhuǎn)問題。iOS10之后用os_unfair_lock代替。比如有如下兩個線程:
線程1(優(yōu)先級高)
線程2(優(yōu)先級低)
如果線程2先進(jìn)去,加鎖了,緊接著線程1又來了,發(fā)現(xiàn)鎖已經(jīng)被線程2加了,那么線程1就會出現(xiàn)忙等。因?yàn)榫€程1優(yōu)先級比較高,所以CPU把資源優(yōu)先分配給在忙等的線程1,此時(shí)優(yōu)先級低的線程2就無法往下面執(zhí)行任務(wù)了。這樣就出現(xiàn)死鎖了。
如果不是自旋鎖,是互斥鎖,就不會出現(xiàn)忙等,線程1就會出現(xiàn)休眠,休眠了,CPU就不會分配資源給線程1了。這樣線程2可以往下執(zhí)行,等執(zhí)行完了,線程1被喚醒后接著就可以往下執(zhí)行了。
2、pthread_mutex:(互斥鎖)等待鎖的線程會處于休眠狀態(tài)。不會占用CPU資源
3、NSConditionLock:(條件鎖,是對mutex的封裝),當(dāng)一條線程進(jìn)去之后,加鎖,由于沒有達(dá)到條件,就會進(jìn)入休眠,并且放開鎖。當(dāng)另一條線程執(zhí)行完任務(wù)達(dá)到條件后就會通知剛剛等待休眠的線程繼續(xù)加鎖執(zhí)行任務(wù)。
4、NSLock也是對mutex的封裝
5、dispatch_semaphore:(信號量)設(shè)置信號量來允許多個線程同時(shí)訪問臨界區(qū)。
6、@synchronized:(也是對mutex的封裝),性能差,它是使用一個對象來生成一把鎖,它底層會有一個哈希表,用來保存對象的地址,一個對象地址對應(yīng)一把鎖。
7、pthread_mutex(recursive):(遞歸鎖),用于遞歸方法執(zhí)行
其中os_unfair_lock的性能是最高的但是iOS10才支持,接下來是信號量,然后是pthread_mutex,性能最差的是@synchronized
自旋鎖和互斥鎖的比較
以下情況用自旋鎖
- 預(yù)計(jì)線程等待鎖的時(shí)間很短
- 加鎖的代碼經(jīng)常被調(diào)用,但競爭情況很少發(fā)生
- CPU資源不緊張
- 多核處理器
以下情況用互斥鎖
- 預(yù)計(jì)線程等待鎖的時(shí)間較長
- 單核處理器
- 臨界區(qū)又IO操作
- 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
- 臨界區(qū)競爭非常激烈
atomic和nonatomic
atomic就是對屬性的getter和setter方法內(nèi)部加了線程同步的鎖,自旋鎖。
注意
但是atomic并不能保證使用屬性的過程是線程安全的。比如一個atomic修飾的MutableArray,在使用數(shù)組添加刪除元素的時(shí)候并不是線程安全的,僅僅是set和get方法是線程安全的。
我們不推薦使用atomic,因?yàn)閷傩哉{(diào)用比較頻繁,如果每次調(diào)用都要加鎖解鎖那么會消耗CPU性能。
讀寫安全方案(文件IO操作)
主要是設(shè)計(jì)兩個接口read和write,一個讀,一個寫,要保證以下情況:
同一時(shí)間允許有多條線程同時(shí)讀
同一時(shí)間只允許有一條線程寫
同一時(shí)間不允許既有寫的操作,又有讀的操作
像這種多讀單寫場景,在iOS中的解決方案有:
1、pthread_rwlock:讀寫鎖
2、dispatch_barrier_async:異步柵欄函數(shù)
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
}

在寫的操作前后進(jìn)行隔離,保證同時(shí)只有寫的操作。