iOS多線程之GCD

本篇文章是iOS多線程系列的第二篇文章,之所以將GCD放在第二篇介紹,是因為理解了GCD后就比較容易理解NSOperation,NSOperation是蘋果對GCD的封裝的產(chǎn)物,以便我們開發(fā)中更好地使用。

本篇文章主要內(nèi)容:

  • GCD是什么
  • 學(xué)習(xí)GCD之前需要理解的東西
  • 常見GCD函數(shù)的用法及實例演示

GCD是什么

GCD(Grand Central Dispatch),是libdispatch的市場名稱,是Apple開發(fā)的一個多線程編程的優(yōu)化方案,Apple也推薦開發(fā)者使用此方案。libdispatch基于線程池的模式管理、執(zhí)行并行任務(wù)。GCD大大降低了開發(fā)者維護線程的成本,使用起來也更加便捷、愉悅。

學(xué)習(xí)GCD之前需要理解的東西

俗話說:磨刀不誤砍柴工,理解多線程的相關(guān)知識對于深入學(xué)習(xí)GCD是十分必要的。作為計算機相關(guān)專業(yè)的學(xué)生,多線程的學(xué)習(xí)是基礎(chǔ)同時也是必不可少,其內(nèi)容豐富,本篇文章只做大致回顧,不再深入探討。注:以下術(shù)語及概念為個人理解表述

串行 VS 并發(fā)

串行和并發(fā),是用來描述任務(wù)與任務(wù)之間的相對關(guān)系。串行指的是當(dāng)前任務(wù)執(zhí)行時其他任務(wù)需要等待,等待當(dāng)前任務(wù)完成后,才能繼續(xù)下一個任務(wù),任務(wù)一個一個地執(zhí)行;并發(fā)指的是當(dāng)前任務(wù)執(zhí)行的同時,其他任務(wù)也可以同時執(zhí)行,不必等待當(dāng)前任務(wù)的完成。大致可以用下圖表示:

串行和并發(fā).png

我們可以看到,串行的情況下,不同的任務(wù)在任何時間段內(nèi)都不會出現(xiàn)重疊執(zhí)行,而并發(fā)的情況下,不同的任務(wù)在某個時間段內(nèi)可能會存在重疊執(zhí)行。

并發(fā) VS 并行

并發(fā)的概念已經(jīng)介紹過了,并行主要是指某個時刻CPU同時執(zhí)行多個任務(wù)的狀態(tài)。嚴(yán)格意義上來說,多核CPU在不同的核上執(zhí)行不同的任務(wù),是真正的并行,對于單核CPU來說,通過上下文切換來使得不同的任務(wù)在一段時間內(nèi)看起來也是被同時執(zhí)行的。大致可以用下圖表示:

并行.png

我們可以看到,對于多核CPU,任務(wù)一和任務(wù)二在同時運行,對于單核CPU,任務(wù)一和任務(wù)二在交替運行。當(dāng)然,這里只是舉個例子,并發(fā)代碼(并發(fā)任務(wù))具體是否并行執(zhí)行,完全取決于系統(tǒng)調(diào)度。并行一定需要并發(fā),但并發(fā)不一定會并行。我們能夠決定哪些代碼需要并發(fā),卻不能決定這些代碼真正并行。

串行隊列 VS 并發(fā)隊列

串行隊列中的任務(wù),會按照提交順序一個一個執(zhí)行,一個任務(wù)執(zhí)行完成后,才能執(zhí)行下一個任務(wù);并發(fā)隊列中的任務(wù),也會按照它們被添加的順序執(zhí)行,但完成時機不確定,例如提交了任務(wù)一,再提交了任務(wù)二,并發(fā)隊列只能保證任務(wù)二在任務(wù)一開始執(zhí)行后才執(zhí)行,但任務(wù)二的結(jié)束時間可能比任務(wù)一早,也可能晚。

同步 VS 異步

同步表示當(dāng)前線程會等待已提交的任務(wù)執(zhí)行完成后繼續(xù)往后執(zhí)行,異步表示當(dāng)前線程提交任務(wù)后,直接繼續(xù)往后執(zhí)行,不會等待已提交的任務(wù),已提交的任務(wù)會在稍后的某個時間點完成。同步任務(wù)會阻塞當(dāng)前線程,而異步任務(wù)不會

死鎖

死鎖表示兩個或多個線程相互等待而導(dǎo)致任何一個線程都不能執(zhí)行。例如線程A等待線程B完成后才執(zhí)行,線程B等待線程A完成后才執(zhí)行,最終結(jié)果是A、B都不能執(zhí)行。死鎖有點類似于OC中的循環(huán)引用,可以對比理解。

上下文切換

上下文切換是指一個線程切換到另外一個線程或進程時保存和恢復(fù)運行狀態(tài)的操作,需要一定的時間和資源開銷。

常見GCD函數(shù)的用法及實例演示

系統(tǒng)隊列類型

系統(tǒng)提供的隊列包括:主隊列(串行隊列)、全局調(diào)度隊列(按優(yōu)先級分為background、low、default和high)、自定義串行隊列、自定義并發(fā)隊列。選擇合適的隊列執(zhí)行合適的任務(wù),是學(xué)習(xí)GCD的重點。注:以下所有示例完整代碼在這里

dispatch_async

 NSString *threadInfo = [NSString stringWithFormat:@"dispatch_async之前線程信息:%@\n\n", [NSThread currentThread]];
    [self fillTextInfo:threadInfo];
    
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [[NSThread currentThread] setName:@"dispatch_async demo"];
        NSMutableString *resultStr = [NSMutableString string];
        [resultStr appendString:[NSString stringWithFormat:@"任務(wù)所在線程信息:%@\n\n", [NSThread currentThread]]];
        [resultStr appendString:@"耗時任務(wù)開始執(zhí)行\(zhòng)n\n"];
        [NSThread sleepForTimeInterval:3.0];//模擬耗時操作
        [resultStr appendString:@"耗時任務(wù)執(zhí)行完畢\n\n"];
        
        //在主線程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            [weakSelf fillTextInfo:resultStr];
        });
    });

    [self fillTextInfo:@"任務(wù)塊后的執(zhí)行代碼\n\n"];

我們通過 dispatch_async 將Block中的代碼(任務(wù))異步提交到優(yōu)先級為Default的全局隊列中執(zhí)行。運行代碼示例后,可以從結(jié)果看出,Block前的代碼執(zhí)行后,立馬執(zhí)行Block后的代碼,并沒有等待Block中的代碼執(zhí)行,在之后的某個時刻,Block中的代碼執(zhí)行完畢,才將更新UI的代碼(任務(wù))提交到了主線程執(zhí)行。下面的表格顯示了不同類型隊列使用dispatch_async的情況:

主隊列 全局隊列 自定義串行隊列 自定義并發(fā)隊列
如果更新UI的代碼不在主線程上,需要通過dispatch_async提交這些代碼到主隊列,以便在稍后的某個時刻會執(zhí)行這些代碼,在非主線程更新UI會出現(xiàn)不可預(yù)料的bug 耗時的、非UI操作通過dispatch_async提交到全局隊列是不錯的選擇,全局隊列還包括系統(tǒng)任務(wù),不只是我們自己的任務(wù) 如果想讓幾個任務(wù)在后臺順序執(zhí)行,可以通過dispatch_async提交到自定義串行列 提交的任務(wù)會并發(fā)執(zhí)行,任務(wù)之間可能會同時訪問某一份數(shù)據(jù)而引起數(shù)據(jù)損壞需要注意

dispatch_after

 [self fillTextInfo:@"準(zhǔn)備執(zhí)行dispatch_after\n\n"];
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakSelf fillTextInfo:@"正在執(zhí)行Block\n\n"];
    });
    
    [self fillTextInfo:@"dispatch_after后面代碼\n\n"];

上面的dispatch_after在延遲2秒后,將Block任務(wù)異步提交到主隊列中,Block后面的代碼先執(zhí)行,Block里面的代碼后執(zhí)行,即使延遲0秒也是這樣的執(zhí)行順序,因為Block中的代碼會在主隊列中排隊,等到前面的任務(wù)結(jié)束后才會執(zhí)行。 dispatch_after和dispatch_async的區(qū)別只是延遲提交了。兩者大同小異,這里就不詳細(xì)介紹了。

dispatch_sync

  NSLog(@"準(zhǔn)備執(zhí)行dispatch_sync");
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"正在執(zhí)行Block");
        [NSThread sleepForTimeInterval:2.0];//模擬耗時操作
    });
    
    NSLog(@"dispatch_sync后面代碼");

上面代碼的執(zhí)行順序是確定的:準(zhǔn)備執(zhí)行dispatch_sync —》正在執(zhí)行Block —》dispatch_sync后面代碼。dispatch_sync使用場景是Block之后的代碼執(zhí)行需要用到Block塊執(zhí)行后的結(jié)果,有前后關(guān)系或者叫依賴。但使用dispatch_sync要注意避免死鎖。下面的代碼就是死鎖:

    NSLog(@"準(zhǔn)備執(zhí)行dispatch_sync");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"正在執(zhí)行Block");
        [NSThread sleepForTimeInterval:2.0];//模擬耗時操作
    });
    
    NSLog(@"dispatch_sync后面代碼");

下面的表格顯示了串行隊列和并發(fā)隊列使用dispatch_sync的注意事項:

串行隊列 并發(fā)隊列
如果在某個串行隊列(不管是主隊列還是自定義串行隊列)向本隊列提交了同步任務(wù),一定會產(chǎn)生死鎖,要慎重 比較適合使用dispatch_sync

dispatch_once

單例模式是一種常用的軟件設(shè)計模式。通過單例模式可以保證系統(tǒng)中一個類只有一個實例。在iOS開發(fā)中,dispatch_once是最完美的方案,且效率很高。直接看代碼:

@implementation NBLPhotoManager

+ (instancetype)sharedManager
{
    static NBLPhotoManager *_manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _manager = [[self alloc] init];
    });
    
    return _manager;
}

@end


for (NSInteger i = 0; i < 5; i++) {
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NBLPhotoManager *photoManager = [NBLPhotoManager sharedManager];
            NSString *tmpStr = [NSString stringWithFormat:@"%@\n\n", photoManager];
            dispatch_async(dispatch_get_main_queue(), ^{
                [weakSelf fillTextInfo:tmpStr];
            });
        });
    }

不論創(chuàng)建多少個對象,它們的地址信息都是一樣的,說明是同一個對象。dispatch_once 使Block中的代碼只能執(zhí)行一次,且線程安全。

Dispatch barrier

- (dispatch_queue_t)concurrentQueue
{
    if (!_concurrentQueue) {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("cn.neebel.GCDDemoBarrier", DISPATCH_QUEUE_CONCURRENT);
        _concurrentQueue = concurrentQueue;
    }

    return _concurrentQueue;
}

#pragma mark - Action

- (void)start
{
    for (NSInteger i = 0; i < 3; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"任務(wù)%@", [NSNumber numberWithInteger:i].stringValue);
        });
    }
    
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"任務(wù)barrier");
    });
    
    for (NSInteger i = 3; i < 6; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSLog(@"任務(wù)%@", [NSNumber numberWithInteger:i].stringValue);
        });
    }
    
}

代碼中我們提交了三個異步任務(wù)到自定義的并發(fā)隊列中,然后異步提交了一個障礙任務(wù),最后又提交了三個異步任務(wù),從執(zhí)行結(jié)果上可得知,不管前面三個和后面三個任務(wù)各自的執(zhí)行順序如何,障礙任務(wù)總是在前三個任務(wù)執(zhí)行之后執(zhí)行,在后三個任務(wù)執(zhí)行之前執(zhí)行。障礙任務(wù)執(zhí)行期間,其他任務(wù)都不會執(zhí)行,在這段時間內(nèi),相當(dāng)于串行隊列。dispatch_barrier_sync的用法大家自行研究。下面表格顯示了dispatch_barrier的使用場景:

串行隊列 全局隊列 自定義并發(fā)隊列
串行隊列不用dispatch_barrier,因為本來就是串行的 最好不要用,會阻塞到系統(tǒng)任務(wù) 比較適合使用

Dispatch group

- (void)startBlockGroup
{
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        
        [NSThread sleepForTimeInterval:1.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
        NSLog(@"任務(wù)1完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        [NSThread sleepForTimeInterval:2.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
        NSLog(@"任務(wù)2完成");
        dispatch_group_leave(group);
    });
    
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"所有任務(wù)完成");
}

任務(wù)一和二不管完成順序如何,NSLog(@"所有任務(wù)完成") 是在兩個任務(wù)都完成之后才能執(zhí)行。dispatch_group_wait的方式會阻塞當(dāng)前線程。

- (void)startUnBlockGroup
{
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        
        [NSThread sleepForTimeInterval:1.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
        NSLog(@"任務(wù)1完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [NSThread sleepForTimeInterval:2.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
        NSLog(@"任務(wù)2完成");
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有任務(wù)完成");
    });
    
    NSLog(@"非阻塞所以會先打印這句話");
}

上面的方式不會阻塞當(dāng)前線程,所以經(jīng)常會用這種方式。

dispatch_apply

- (void)start
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_apply(2, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
        switch (i) {
            case 0:
            {
                dispatch_group_enter(group);
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
                    [NSThread sleepForTimeInterval:1.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
                    NSLog(@"任務(wù)1完成");
                    dispatch_group_leave(group);
                });
            }
                break;
                
            case 1:
            {
                dispatch_group_enter(group);
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
                    [NSThread sleepForTimeInterval:2.0];//模擬耗時任務(wù),可以調(diào)整時間模擬任務(wù)一和二的完成順序
                    NSLog(@"任務(wù)2完成");
                    dispatch_group_leave(group);
                });
            }
                break;
                
            default:
                break;
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有任務(wù)完成");
    });
}

dispatch_apply類似于for循環(huán),但它的迭代是并發(fā)執(zhí)行的,而for循環(huán)是順序執(zhí)行的。使用時要考慮其資源開銷值不值得。

信號量

信號量機制比較復(fù)雜,用處也很多,例如經(jīng)典的哲學(xué)家進餐問題。深入理解信號量機制需要大家花費更多的時間和精力研究。下面的代碼使用信號量解決線程安全問題,希望能起到拋磚引玉的作用。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"信號量";
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.startButton];
    [self.view addSubview:self.infoTextView];
    semaphore = dispatch_semaphore_create(1);
}

- (void)start
{
    __weak typeof(self) weakSelf = self;
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(self.concurrentQueue, ^{
            NSObject *object = [weakSelf buildAnObj];
            NSLog(@"%@", object);
        });
    }
    
    for (NSInteger i = 0; i < 5; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSObject *object = [weakSelf buildAnObj];
            NSLog(@"%@", object);
        });
    }
}
//目的是只創(chuàng)建一個對象
- (NSObject *)buildAnObj
{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    if (!self.obj) {//這個判斷在多線程訪問時是不安全的,可能存在多個線程同時進入執(zhí)行的情況,使用信號量機制充當(dāng)鎖就沒問題了
        self.obj = [[NSObject alloc] init];
    }
    dispatch_semaphore_signal(semaphore);
    
    return self.obj;
}

上面的代碼無論執(zhí)行多少次都只會創(chuàng)建一個對象,原因是dispatch_semaphore_create(1),創(chuàng)建了一個值為1的信號量,當(dāng)一個線程A執(zhí)行了dispatch_semaphore_wait后,信號量的值會減1,變?yōu)?,這時候其他線程就會等待,等到A執(zhí)行dispatch_semaphore_signal后,信號量才會加1,其他線程才會繼續(xù)執(zhí)行,作用類似于線程鎖,從而保證了線程安全。

至此,iOS多線程之GCD就介紹完了,文中沒有用到很多專業(yè)解釋,都是根據(jù)自己的理解表述的,目的是便于大家理解,有不妥或不正確的還請指正。

最后編輯于
?著作權(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)容

  • 多線程 在iOS開發(fā)中為提高程序的運行效率會將比較耗時的操作放在子線程中執(zhí)行,iOS系統(tǒng)進程默認(rèn)啟動一個主線程,用...
    郭豪豪閱讀 2,719評論 0 4
  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 496評論 0 0
  • 原創(chuàng)文章 轉(zhuǎn)載請注明出處, 謝謝! (~ o ~)Y 本文思維導(dǎo)圖 GCD是什么 全稱是 Grand Centra...
    Jimmy_P閱讀 4,807評論 10 66
  • GCD (Grand Central Dispatch) :iOS4 開始引入,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,435評論 0 2
  • iOS多線程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst閱讀 1,327評論 0 0

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