iOS 多線程

參考資料:
iOS多線程
iOS GCD

多線程問題在iOS中目前有4套多線程方案,他們分別是:

  • Pthreads
  • NSthread
  • GCD
  • NSOperation

Pthreads

這是一套在很多操作系統(tǒng)通用的多線程API,不過這是基于c語言的框架,在iOS實際開發(fā)中很少使用。

  1. 包含頭文件:
    #import <pthread.h>
  2. 創(chuàng)建線程,并執(zhí)行任務(wù):
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 創(chuàng)建一個新的線程并自動執(zhí)行
    pthread_t thread;
    pthread_create(&thread, NULL, pthreadStart, NULL);
}

void *pthreadStart() {
    NSLog(@"%@", [NSThread currentThread]);
    return NULL;
}
  1. 輸出打印:
    <NSThread: 0x6040002719c0>{number = 3, name = (null)}

NSthread

這套方案是蘋果封裝的多線程解決方案??梢灾苯硬倏鼐€程對象。但是,它的生命周期還是需要我們手動管理,所以這套方案使用頻率不高,下面來看看它的一些用法。

  1. 創(chuàng)建線程并啟動
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[thread start];
  1. 創(chuàng)建線程并自動啟動
[NSThread detachNewThreadSelector:@selector(threadStart) toTarget:self withObject:nil];
  1. 使用NSObject的方法創(chuàng)建線程并自動啟動
[self performSelectorInBackground:@selector(threadStart) withObject:nil];
  1. 其他常用方法
//取消線程
- (void)cancel;

//啟動線程
- (void)start;

//判斷某個線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當(dāng)前線程暫停一段時間,或者暫停到某個時刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

GCD

Grand Central Dispatch,它是蘋果為多核的并行運(yùn)算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是 c語言,不過由于使用了Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。

1. 任務(wù)和隊列

GCD中,加入了兩個非常重要的概念:任務(wù)隊列

  • 任務(wù):即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間的區(qū)別是 是否會創(chuàng)建新的線程

同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢!
如果是 同步(sync) 操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運(yùn)行。
如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。

  • 隊列:用于存放任務(wù)。一共有兩種隊列, 串行隊列 和 并行隊列。
    串行隊列 中的任務(wù),GCD 會 FIFO(先進(jìn)先出) 地取出來一個,執(zhí)行一個,然后取下一個,這樣一個一個的執(zhí)行。
    并行隊列 中的任務(wù),GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務(wù)都是一起執(zhí)行的。不過需要注意,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多,它并不會讓所有任務(wù)同時執(zhí)行。

2. 創(chuàng)建隊列

  • 主隊列:這是一個特殊的 串行隊列。什么是主隊列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。
//OBJECTIVE-C
dispatch_queue_t queue = ispatch_get_main_queue();
  • 自己創(chuàng)建的隊列:自己可以創(chuàng)建 串行隊列, 也可以創(chuàng)建 并行隊列??聪旅娴拇a(代碼已更新),它有兩個參數(shù),第一個上面已經(jīng)說了,第二個才是最重要的。
    第二個參數(shù)用來表示創(chuàng)建的隊列是串行的還是并行的,傳入 DISPATCH_QUEUE_SERIALNULL 表示創(chuàng)建串行隊列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊列。
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
  • 全局并行隊列:只要是并行任務(wù)一般都加入到這個隊列。這是系統(tǒng)提供的一個并發(fā)隊列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3. 創(chuàng)建任務(wù)

  • 同步任務(wù):會阻塞當(dāng)前線程 (SYNC)
dispatch_sync(<#queue#>, ^{
    //code here
    NSLog(@"%@", [NSThread currentThread]);
});
  • 異步任務(wù):不會阻塞當(dāng)前線程 (ASYNC)
dispatch_async(<#queue#>, ^{
    //code here
    NSLog(@"%@", [NSThread currentThread]);
});

4. 隊列組

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當(dāng)這個組里所有的任務(wù)都執(zhí)行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。

//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});

5. 柵欄方法

  • 我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作。這樣我們就需要一個相當(dāng)于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來,當(dāng)然這里的操作組里可以包含一個或多個任務(wù)。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----2-----%@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"----barrier-----%@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"----4-----%@", [NSThread currentThread]);
});

6. 延時執(zhí)行

  • 當(dāng)我們需要延遲執(zhí)行一段代碼時,就需要用到GCD的dispatch_after方法。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
   NSLog(@"run-----");
});

7. 一次性代碼

  • 我們在創(chuàng)建單例、或者有整個程序運(yùn)行過程中只執(zhí)行一次的代碼時,我們就用到了GCD的dispatch_once方法。使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});

8. 快速迭代方法

  • 通常我們會用for循環(huán)遍歷,但是GCD給我們提供了快速迭代的方法dispatch_apply,使我們可以同時遍歷。比如說遍歷0~5這6個數(shù)字,for循環(huán)的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數(shù)字。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});

NSOperation

NSOperation 是蘋果公司對 GCD 的封裝,完全面向?qū)ο?,所以使用起來更好理解?大家可以看到NSOperationNSOperationQueue分別對應(yīng) GCD 的任務(wù)隊列。操作步驟如下:

  1. 將要執(zhí)行的任務(wù)封裝到一個NSOperation對象中。
  2. 將此任務(wù)添加到一個NSOperationQueue對象中。

注:
NSOperation只是一個抽象類,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)。分別是:NSInvocationOperationNSBlockOperation。創(chuàng)建一個Operation后,需要調(diào)用start方法來啟動任務(wù),它會 默認(rèn)在當(dāng)前隊列同步執(zhí)行。當(dāng)然你也可以在中途取消一個任務(wù),只需要調(diào)用其cancel方法即可。

  • NSInvocationOperation:
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//2.開始執(zhí)行
[operation start];
  • NSBlockOperation
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//2.開始任務(wù)
[operation start];

NSBlockOperation默認(rèn)會在當(dāng)前線程執(zhí)行。但它還有一個方法:addExecutionBlock:,通過這個方法可以給Operation添加多個執(zhí)行 Block。這樣 Operation 中的任務(wù) 會并發(fā)執(zhí)行,它會在主線程和其它的多個線程 執(zhí)行這些任務(wù)。

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

for (int i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"execution - %@", [NSThread currentThread]);
    }];
}

[operation start];

輸出打?。?/p>

<NSThread: 0x60000046c880>{number = 9, name = (null)}
execution - <NSThread: 0x604000078ac0>{number = 1, name = main}
execution - <NSThread: 0x600000469280>{number = 11, name = (null)}
execution - <NSThread: 0x60400026cb40>{number = 10, name = (null)}
execution - <NSThread: 0x60000046c880>{number = 9, name = (null)}
execution - <NSThread: 0x604000078ac0>{number = 1, name = main}
  • 自定義Operation

除了上面的兩種Operation以外,我們還可以自定義Operation。自定義Operation需要繼承NSOperation類,并實現(xiàn)其main()方法,因為在調(diào)用start()方法的時候,內(nèi)部會調(diào)用main()方法完成相關(guān)邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實現(xiàn)什么功能都可以寫在里面。除此之外,你還需要實現(xiàn)cancel()在內(nèi)的各種方法。

創(chuàng)建隊列

看過上面的內(nèi)容就知道,我們可以調(diào)用一個NSOperation對象的start()方法來啟動這個任務(wù),但是這樣做他們默認(rèn)是 同步執(zhí)行 的。就算是addExecutionBlock方法,也會在 當(dāng)前線程和其他線程 中執(zhí)行,也就是說還是會占用當(dāng)前線程。這是就要用到隊列NSOperationQueue了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調(diào)用任務(wù)的start()方法。

  • 主隊列

細(xì)心的同學(xué)就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當(dāng)然啦,說的是iOS中,像pthread這種多系統(tǒng)的方案并沒有,因為UI線程理論需要每種操作系統(tǒng)自己定制)。這是一個特殊的線程,必須串行。所以添加到主隊列的任務(wù)都會一個接一個地排著隊在主線程處理。

NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他隊列

因為主隊列比較特殊,所以會單獨(dú)有一個類方法來獲得主隊列。那么通過初始化產(chǎn)生的隊列就是其他隊列了,因為只有這兩種隊列,除了主隊列,其他隊列就不需要名字了。

注意:其他隊列的任務(wù)會在其他線程并行執(zhí)行。

//1.創(chuàng)建一個其他隊列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

//4.隊列添加任務(wù)
[queue addOperation:operation];

打印輸出

2015-07-28 20:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
2015-07-28 20:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}

OK, 這時應(yīng)該發(fā)問了,大家將NSOperationQueueGCD的隊列 相比較就會發(fā)現(xiàn),這里沒有串行隊列,那如果我想要10個任務(wù)在其他線程串行的執(zhí)行怎么辦?

這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue有一個參數(shù)maxConcurrentOperationCount最大并發(fā)數(shù),用來設(shè)置最多可以讓多少個任務(wù)同時執(zhí)行。當(dāng)你把它設(shè)置為 1 的時候,他不就是串行了嘛!

NSOperationQueue還有一個添加任務(wù)的方法,- (void)addOperationWithBlock:(void (^)(void))block;,這是不是和GCD差不多?這樣就可以添加一個任務(wù)到隊列中了,十分方便。

NSOperation有一個非常實用的功能,那就是添加依賴。比如有 3 個任務(wù):A: 從服務(wù)器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務(wù)器。這時就可以用到依賴了:

//1.任務(wù)一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3.0];
}];

//2.任務(wù)二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務(wù)三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設(shè)置依賴
[operation1 addDependency:operation3];      //任務(wù)二依賴任務(wù)一
[operation3 addDependency:operation2];      //任務(wù)三依賴任務(wù)二

//5.創(chuàng)建隊列并加入任務(wù)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

打印結(jié)果

打水印   - <NSThread: 0x600000276900>{number = 3, name = (null)}
上傳圖片 - <NSThread: 0x604000462400>{number = 4, name = (null)}
下載圖片 - <NSThread: 0x600000276900>{number = 3, name = (null)}
  • 注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
  • 可以使用removeDependency來解除依賴關(guān)系。
  • 可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務(wù)身上的,和隊列沒關(guān)系。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關(guān)多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,141評論 6 18
  • 主隊列 細(xì)心的同學(xué)就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當(dāng)然啦,說的是iOS中,像 pthread 這種多系統(tǒng)...
    京北磊哥閱讀 416評論 0 1
  • 在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項。當(dāng)然也會給出幾種多線程的案...
    張戰(zhàn)威ican閱讀 691評論 0 0
  • 學(xué)習(xí)多線程,轉(zhuǎn)載兩篇大神的帖子,留著以后回顧!第一篇:關(guān)于iOS多線程,你看我就夠了 第二篇:GCD使用經(jīng)驗與技巧...
    John_LS閱讀 732評論 0 3
  • 一、前言 本篇博文介紹的是iOS中常用的幾個多線程技術(shù): NSThread GCD NSOperation 由于a...
    和玨貓閱讀 658評論 0 1

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