iOS開發(fā)進階-多線程技術(shù)

多線程.png

iOS中多線程

首先看一道面試題

iOS中多線程有哪些實現(xiàn)方案?

技術(shù)方案 簡介 語言 線程聲明周期 使用頻率
pthread 1. 一套通用的多線程API
2. 跨平臺/可移植
3. 使用難度大
C 程序員管理 幾乎不用
NSThread 1.面向?qū)ο?br> 2.簡單易用直接操作線程對象 OC 程序員管理 偶爾使用
GCD 1.旨在替代NSThread等線程技術(shù)
2.充分利用設(shè)備的多核
C 自動管理 經(jīng)常使用
NSOperation 1.基于GCD
2.比GCD多了一些更實用的函數(shù)
OC 自動管理 經(jīng)常使用

iOS中,多線程一般有三種方案GCD、NSOperationNSThread

這一篇文章,會學(xué)習(xí)iOS中關(guān)于多線程相關(guān)的問題,以及面試中的問題。

一、GCD

GCD相關(guān)問題一般分為三個方面:首先,同步/異步串行/并發(fā)問題;其次,dispatch_barrier_async異步柵欄調(diào)用,解決多讀單寫問題;最后,dispatch_group使用和理解。

GCD中有兩種用來執(zhí)行任務(wù)的函數(shù):同步和異步;同時還有兩種類型的隊列:并發(fā)和串行隊列。并發(fā)隊列讓多個任務(wù)并發(fā)執(zhí)行,自動開啟多個線程同時執(zhí)行任務(wù)。并發(fā)功能只在異步函數(shù)下才生效。

并發(fā)隊列 手動創(chuàng)建的串行隊列 主隊列
同步 沒有開辟新線程
串行執(zhí)行
沒有開辟新線程
串行執(zhí)行
沒有開辟新線程
串行執(zhí)行
異步 有開辟新線程
并發(fā)執(zhí)行
有開辟新線程
串行執(zhí)行
沒有開辟新線程
串行執(zhí)行

注意:使用同步函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡主當(dāng)前的串行隊列,產(chǎn)生死鎖。

1.1 同步/異步 && 串行/并發(fā)

存在四種組合方案:

// 同步 + 串行
dispatch_sync(serial_queue, ^{
//任務(wù)
});
// 異步 + 串行
dispatch_async(serial_queue, ^{
//任務(wù)
});
// 同步 + 并發(fā)
dispatch_sync(concurrent_queue, ^{
//任務(wù)
});
// 異步 + 并發(fā)
dispatch_async(concurrent_queue, ^{
//任務(wù)
});

1.1.1 同步 + 串行

首先看一道面試題

- (void)viewDidLoad {
  dispatch_sync(dispatch_get_main_queue(), ^{
       [self doSomething];
  });
}

上面這道面試題,存在什么問題?

產(chǎn)生死鎖。隊列引起循環(huán)等待。因為,viewDidLoad()進入主隊列,執(zhí)行過程中會將block添加到主隊列中。viewDidLoad()需要等待block執(zhí)行完成后才能結(jié)束,由于主隊列先進先出的block需要viewDidLoad()執(zhí)行完畢才能執(zhí)行。因此導(dǎo)致隊列循環(huán)等待的問題。

上面的問題理解,在來看一個問題。

- (void)viewDidLoad {
  dispatch_sync(serial_queue, ^{
       [self doSomething];
  });
}

上面這道題,有什么問題?

沒有問題。這里是將block添加到單獨的串行隊列。viewDidLoad()在主隊列中在主線程中執(zhí)行,在其執(zhí)行過程中調(diào)用block添加到串行隊列中,在主線程中執(zhí)行。同步方式提交任務(wù),無論在串行隊列還是并發(fā)隊列都會在當(dāng)前線程中執(zhí)行

1.1.2 同步 + 并發(fā)

問題

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSLog(@"1");
        dispatch_sync(global_queue, ^{
            NSLog(@"2");
            dispatch_sync(global_queue, ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");
    }
    return 0;
}

上面這段代碼的輸出結(jié)果?

輸出結(jié)果:12345。同步方式提交任務(wù),無論在串行隊列還是并發(fā)隊列都會在當(dāng)前線程中執(zhí)行。

思考,如果換成串行隊列呢?

1.1.3 異步 + 串行

略...

1.1.4 異步 + 并發(fā)

面試題

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(global_queue, ^{
        NSLog(@"1");
        [self performSelector:@selector(printLog) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
}
- (void)printLog {
    NSLog(@"2");
}

上述代碼執(zhí)行的結(jié)果?

結(jié)果:13。提交異步任務(wù)到并發(fā)隊列,任務(wù)調(diào)用了performSelector:withObject:afterDelay:。由于GCD提交的任務(wù)是在某個子線程中執(zhí)行,子線程沒有RunLoop。由于performSelector:withObject:afterDelay:需要RunLoop才可生效,所以方法不執(zhí)行。這個問題,考察performSelector:withObject:afterDelay:內(nèi)部實現(xiàn)。

任務(wù)和隊列示例代碼:任務(wù)和隊列Demo包含面試題講解

二、多讀單寫解決方案

  • pthread_rwlock: 讀寫鎖
  • dispatch_barrier_async() 異步柵欄調(diào)用

怎么利用GCD實現(xiàn)多讀單寫?或者如何實現(xiàn)多讀單寫?

2.1 什么是多讀單寫?

讀者和讀者,并發(fā)。
讀者和寫者,互斥。
寫者與寫者,互斥。

2.2 解決方法

   dispatch_async(global_queue, ^{
        NSLog(@"讀取1");
    });
   dispatch_async(global_queue, ^{
        NSLog(@"讀取2");
    });
   dispatch_barrier_async(global_queue, ^{
        NSLog(@"寫入1");
    });
   dispatch_async(global_queue, ^{
        NSLog(@"讀取3");
    });
   dispatch_async(global_queue, ^{
        NSLog(@"讀取4");
    });

dispatch_barrier_async函數(shù)會等待追加到并發(fā)隊列上的并行執(zhí)行的處理全部結(jié)束之后,在將指定的處理追加到該并發(fā)隊列中。然后等dispatch_barrier_async函數(shù)追加的處理執(zhí)行完畢后,并發(fā)隊列才恢復(fù)為一般的動作,追加到并發(fā)隊列的處理又開始并行執(zhí)行。

三、dispatch_group_async()

面試題:

如何用GCD 實現(xiàn):A、B、C三個任務(wù)并發(fā),完成后執(zhí)行任務(wù)D?

實現(xiàn)追加到并發(fā)隊列中的多個任務(wù)全部結(jié)束后再執(zhí)行想執(zhí)行的任務(wù)。無論向什么樣的隊列中追加處理,使用Dispatch Group都可監(jiān)視這些處理執(zhí)行的結(jié)束。一旦檢測到所有處理執(zhí)行結(jié)束,該Dispatch Group與隊列相同。

dispatch_group_async()dispatch_async()函數(shù)相同,都追加Block到指定的Dispatch Queue中。當(dāng)組中所有任務(wù)都執(zhí)行完成后,dispatch_group_notify()執(zhí)行Block中的內(nèi)容。

示例代碼:

// dispatch_group_notify
- (void)test {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.lqq.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)1 - %@", [NSThread currentThread]);
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)2 - %@", [NSThread currentThread]);
        }
    });
    //--------------示例1-------------------
    // 寫法一, 等上面的任務(wù)執(zhí)行完成后,才會在主隊列中執(zhí)行任務(wù)3
//    dispatch_group_notify(group, queue, ^{
//        dispatch_async(dispatch_get_main_queue(), ^{
//            for (int i = 0; i < 5; i++) {
//                NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
//            }
//        });
//    });
    
    //寫法二:直接在主隊列中執(zhí)行
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 5; i++) {
//            NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
//        }
//    });
    //--------------示例2-------------------
    // 如果有多個notify會怎么執(zhí)行呢?
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3 - %@", [NSThread currentThread]);
        }
    });
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)4 - %@", [NSThread currentThread]);
        }
    });
    // 任務(wù)3和任務(wù)4是交替執(zhí)行的
}

另外,也可以使用dispatch_group_wait,如下:

   // 監(jiān)控任務(wù)是否完成,當(dāng)完成時會返回0,不完成一直等待。
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (result == 0) {
        NSLog(@"全部任務(wù)執(zhí)行完成");
    }

線程組示例代碼:線程組API使用Demo

四、NSOperation

需要和NSOperationQueue配合使用來實現(xiàn)多線程。優(yōu)勢和特點:添加任務(wù)依賴、任務(wù)執(zhí)行狀態(tài)控制、控制最大并發(fā)量。

任務(wù)狀態(tài)的控制

isReady
isExecuting
isFinished
isCanceled

如果重寫main方法,底層控制變更任務(wù)執(zhí)行完成狀態(tài),以及任務(wù)退出。

如果重寫start方法,自行控制任務(wù)狀態(tài)。

面試題

系統(tǒng)是怎樣移除一個isFinished=YES的NSOperation的?答案:KVO

五、NSThread

啟動流程

start() -> 創(chuàng)建pthread -> main() ->[target performSelector:selector] -> exit()

如何實現(xiàn)常駐進程?
通過使用NSThread和RunLoop實現(xiàn)。

附錄

GCD API介紹:
dispatch_queue_create(名稱,類型)
類型為NULL時,為串行隊列;為DISPATCH_QUEUE_CONCUREENT,為并行隊列.。

需要手動release釋放隊列
dispatch_release(隊列)

主隊列,串行
dispatch_get_main_queue()
dispatch_get_global_queue(優(yōu)先級,0)

優(yōu)先級有四種情況:
    高,默認,低,后臺
    
為自己創(chuàng)建的隊列設(shè)置優(yōu)先級

dispatch_set_target_queue(自定義隊列,其他已知優(yōu)先級的隊列) 

dispatch_after(時間,隊列,Block)
時間:dispatch_time_t 

dispatch_group 監(jiān)聽所有任務(wù)結(jié)束。

dispatch_group_create() //創(chuàng)建一個隊列組,需要手動release
dispatch_group_async(組,隊列,block)
dispatch_group_notify(組,隊列,block) // 所有任務(wù)都結(jié)束后調(diào)用
也可以使用dispatch_group_wait()

dispatch_barrier_async() 一般用于數(shù)據(jù)庫操作和文件讀寫。

同步任務(wù)
dispatch_sync(隊列,block) 

異步任務(wù)
dispatch_async(隊列,block)

指定執(zhí)行次數(shù)
dispatch_apply(次數(shù),隊列,block)


掛起
dispatch_suspend(隊列)

恢復(fù)
dispatch_resume(隊列)

信號量
Dispatch Semaphore是持有計數(shù)的信號,該計數(shù)是多線程編程中的計數(shù)類型信號。

dispatch_semaphore_create(技術(shù)值)
dispatch_semaphore_wait(semaphore, 時間)
dispatch_semaphore_signal(semaphore)
dispatch_reliease(semaphore)

// 示例
// 創(chuàng)建一個對象,默認的計數(shù)值為0,等待。當(dāng)計數(shù)值大于1或者等于1時,減去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 發(fā)送計數(shù)值加一信號
dispatch_semaphore_signal(semaphore);
// 等待計數(shù)值變化,第二個參數(shù)是等待時間,這里是永久等待。
// 當(dāng)計數(shù)值大于0時,返回0。等于0不會返回任何值。
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

if (result == 0) {
    NSLog(@"執(zhí)行排他性操作");
}


dispatch_once函數(shù)是保證在應(yīng)用程序執(zhí)行中只執(zhí)行一次的API。


dispatch I/O 分割讀取,提高讀取效率。

小結(jié)

通過幾道面試題理解iOS中幾種多線程解決方案。筆者認為該課程可以用作知識梳理,將解讀的內(nèi)容不是很全面,其他的內(nèi)容可以看《小馬哥底層原理視頻》。

參考文章:
細說 NSOperation

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

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

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