小碼哥底層原理筆記:多線程

多線程方案:

多線程方案比較

比較常用的是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ì)列執(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í)只有寫的操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容