iOS-GCD

  • GCD---同步/異步 ,串行/并發(fā)
  • 死鎖
  • GCD任務(wù)執(zhí)行順序
  • dispatch_barrier_async
  • dispatch_group_async
  • Dispatch Semaphore
  • 延時(shí)函數(shù)(dispatch_after)
  • 使用dispatch_once實(shí)現(xiàn)單例

一、GCD---隊(duì)列

iOS中,有GCD、NSOperation、NSThread等幾種多線程技術(shù)方案。

而GCD共有三種隊(duì)列類型:
main queue:通過dispatch_get_main_queue()獲得,這是一個與主線程相關(guān)的串行隊(duì)列。

global queue:全局隊(duì)列是并發(fā)隊(duì)列,由整個進(jìn)程共享。存在著高、中、低三種優(yōu)先級的全局隊(duì)列。調(diào)用dispath_get_global_queue并傳入優(yōu)先級來訪問隊(duì)列。

自定義隊(duì)列:通過函數(shù)dispatch_queue_create創(chuàng)建的隊(duì)列。

二、 死鎖

死鎖就是隊(duì)列引起的循環(huán)等待

1、一個比較常見的死鎖例子:主隊(duì)列同步
- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_sync(dispatch_get_main_queue(), ^{

        NSLog(@"deallock");
    });
    // Do any additional setup after loading the view, typically from a nib.
}

在主線程中運(yùn)用主隊(duì)列同步,也就是把任務(wù)放到了主線程的隊(duì)列中。
同步對于任務(wù)是立刻執(zhí)行的,那么當(dāng)把任務(wù)放進(jìn)主隊(duì)列時(shí),它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù),viewDidLoad才會繼續(xù)向下執(zhí)行。
而viewDidLoad和任務(wù)都是在主隊(duì)列上的,由于隊(duì)列的先進(jìn)先出原則,任務(wù)又需等待viewDidLoad執(zhí)行完畢后才能繼續(xù)執(zhí)行,viewDidLoad和這個任務(wù)就形成了相互循環(huán)等待,就造成了死鎖。
想避免這種死鎖,可以將同步改成異步dispatch_async,或者將dispatch_get_main_queue換成其他串行或并行隊(duì)列,都可以解決。

2、同樣,下邊的代碼也會造成死鎖:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{

        dispatch_sync(serialQueue, ^{

            NSLog(@"deadlock");
        });
    });

外面的函數(shù)無論是同步還是異步都會造成死鎖。
這是因?yàn)槔锩娴娜蝿?wù)和外面的任務(wù)都在同一個serialQueue隊(duì)列內(nèi),又是同步,這就和上邊主隊(duì)列同步的例子一樣造成了死鎖
解決方法也和上邊一樣,將里面的同步改成異步dispatch_async,或者將serialQueue換成其他串行或并行隊(duì)列,都可以解決

    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    dispatch_async(serialQueue, ^{

        dispatch_sync(serialQueue2, ^{

            NSLog(@"deadlock");
        });
    });

這樣是不會死鎖的,并且serialQueue和serialQueue2是在同一個線程中的。

三、GCD任務(wù)執(zhí)行順序

1、串行隊(duì)列先異步后同步
    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    NSLog(@"1");

    dispatch_async(serialQueue, ^{

         NSLog(@"2");
    });

    NSLog(@"3");

    dispatch_sync(serialQueue, ^{

        NSLog(@"4");
    });

    NSLog(@"5");

打印順序是13245
原因是:
首先先打印1
接下來將任務(wù)2其添加至串行隊(duì)列上,由于任務(wù)2是異步,不會阻塞線程,繼續(xù)向下執(zhí)行,打印3
然后是任務(wù)4,將任務(wù)4添加至串行隊(duì)列上,因?yàn)槿蝿?wù)4和任務(wù)2在同一串行隊(duì)列,根據(jù)隊(duì)列先進(jìn)先出原則,任務(wù)4必須等任務(wù)2執(zhí)行后才能執(zhí)行,又因?yàn)槿蝿?wù)4是同步任務(wù),會阻塞線程,只有執(zhí)行完任務(wù)4才能繼續(xù)向下執(zhí)行打印5
所以最終順序就是13245。
這里的任務(wù)4在主線程中執(zhí)行,而任務(wù)2在子線程中執(zhí)行。
如果任務(wù)4是添加到另一個串行隊(duì)列或者并行隊(duì)列,則任務(wù)2和任務(wù)4無序執(zhí)行(可以添加多個任務(wù)看效果)

2、performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [self performSelector:@selector(test:) withObject:nil afterDelay:0];
    });

這里的test方法是不會去執(zhí)行的,原因在于

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

這個方法要創(chuàng)建提交任務(wù)到runloop上的,而gcd底層創(chuàng)建的線程是默認(rèn)沒有開啟對應(yīng)runloop的,所有這個方法就會失效。
而如果將dispatch_get_global_queue改成主隊(duì)列,由于主隊(duì)列所在的主線程是默認(rèn)開啟了runloop的,就會去執(zhí)行(將dispatch_async改成同步,因?yàn)橥绞窃诋?dāng)前線程執(zhí)行,那么如果當(dāng)前線程是主線程,test方法也是會去執(zhí)行的)。

四、dispatch_barrier_async

1、問:怎么用GCD實(shí)現(xiàn)多讀單寫?

多讀單寫的意思就是:可以多個讀者同時(shí)讀取數(shù)據(jù),而在讀的時(shí)候,不能去寫入數(shù)據(jù)。并且,在寫的過程中,不能有其他寫者去寫。即讀者之間是并發(fā)的,寫者與讀者或其他寫者是互斥的。

image

這里的寫處理就是通過柵欄的形式去寫。
就可以用dispatch_barrier_sync(柵欄函數(shù))去實(shí)現(xiàn)

2、dispatch_barrier_sync的用法:
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    for (NSInteger i = 0; i < 10; i++) {

        dispatch_sync(concurrentQueue, ^{

            NSLog(@"%zd",i);
        });
    }

    dispatch_barrier_sync(concurrentQueue, ^{

        NSLog(@"barrier");
    });

    for (NSInteger i = 10; i < 20; i++) {

        dispatch_sync(concurrentQueue, ^{

            NSLog(@"%zd",i);
        });
    }

這里的dispatch_barrier_sync上的隊(duì)列要和需要阻塞的任務(wù)在同一隊(duì)列上,否則是無效的。
從打印上看,任務(wù)0-9和任務(wù)任務(wù)10-19因?yàn)槭钱惒讲l(fā)的原因,彼此是無序的。而由于柵欄函數(shù)的存在,導(dǎo)致順序必然是先執(zhí)行任務(wù)0-9,再執(zhí)行柵欄函數(shù),再去執(zhí)行任務(wù)10-19。

  • dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一個柵欄函數(shù)在執(zhí)行中,它會等待柵欄函數(shù)執(zhí)行完)
  • dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一個柵欄函數(shù)在異步執(zhí)行中,它會立馬返回)
    而dispatch_barrier_sync和dispatch_barrier_async的區(qū)別也就在于會不會阻塞當(dāng)前線程
    比如,上述代碼如果在dispatch_barrier_async后隨便加一條打印,則會先去執(zhí)行該打印,再去執(zhí)行任務(wù)0-9和柵欄函數(shù);而如果是dispatch_barrier_sync,則會在任務(wù)0-9和柵欄函數(shù)后去執(zhí)行這條打印。
3、則可以這樣設(shè)計(jì)多讀單寫:
- (id)readDataForKey:(NSString *)key
{
    __block id result;

    dispatch_sync(_concurrentQueue, ^{

        result = [self valueForKey:key];
    });

    return result;
}

- (void)writeData:(id)data forKey:(NSString *)key
{
    dispatch_barrier_async(_concurrentQueue, ^{

        [self setValue:data forKey:key];
    });
}

五、dispatch_group_async

場景:在n個耗時(shí)并發(fā)任務(wù)都完成后,再去執(zhí)行接下來的任務(wù)。比如,在n個網(wǎng)絡(luò)請求完成后去刷新UI頁面。

dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_t group = dispatch_group_create();

    for (NSInteger i = 0; i < 10; i++) {

        dispatch_group_async(group, concurrentQueue, ^{

            sleep(1);

            NSLog(@"%zd:網(wǎng)絡(luò)請求",i);
        });
    }

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{

        NSLog(@"刷新頁面");
    });

深入理解GCD之dispatch_group
六、Dispatch Semaphore

GCD 中的信號量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號。

Dispatch Semaphore 提供了三個函數(shù)

1.dispatch_semaphore_create:創(chuàng)建一個Semaphore并初始化信號的總量
2.dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加1
3.dispatch_semaphore_wait:可以使總信號量減1,當(dāng)信號總量為0時(shí)就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。

Dispatch Semaphore 在實(shí)際開發(fā)中主要用于:

  • 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
  • 保證線程安全,為線程加鎖
1、保持線程同步:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    __block NSInteger number = 0;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        number = 100;

        dispatch_semaphore_signal(semaphore);
    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    NSLog(@"semaphore---end,number = %zd",number);

dispatch_semaphore_wait加鎖阻塞了當(dāng)前線程,dispatch_semaphore_signal解鎖后當(dāng)前線程繼續(xù)執(zhí)行

2、保證線程安全,為線程加鎖:

在線程安全中可以將dispatch_semaphore_wait看作加鎖,而dispatch_semaphore_signal看作解鎖
首先創(chuàng)建全局變量

 _semaphore = dispatch_semaphore_create(1);

注意到這里的初始化信號量是1。

- (void)asyncTask
{

    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

    count++;

    sleep(1);

    NSLog(@"執(zhí)行任務(wù):%zd",count);

    dispatch_semaphore_signal(_semaphore);
}

異步并發(fā)調(diào)用asyncTask

  for (NSInteger i = 0; i < 100; i++) {

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            [self asyncTask];
        });
    }

然后發(fā)現(xiàn)打印是從任務(wù)1順序執(zhí)行到100,沒有發(fā)生兩個任務(wù)同時(shí)執(zhí)行的情況。

原因如下:
在子線程中并發(fā)執(zhí)行asyncTask,那么第一個添加到并發(fā)隊(duì)列里的,會將信號量減1,此時(shí)信號量等于0,可以執(zhí)行接下來的任務(wù)。而并發(fā)隊(duì)列中其他任務(wù),由于此時(shí)信號量不等于0,必須等當(dāng)前正在執(zhí)行的任務(wù)執(zhí)行完畢后調(diào)用dispatch_semaphore_signal將信號量加1,才可以繼續(xù)執(zhí)行接下來的任務(wù),以此類推,從而達(dá)到線程加鎖的目的。

六、延時(shí)函數(shù)(dispatch_after)

dispatch_after能讓我們添加進(jìn)隊(duì)列的任務(wù)延時(shí)執(zhí)行,該函數(shù)并不是在指定時(shí)間后執(zhí)行處理,而只是在指定時(shí)間追加處理到dispatch_queue

//第一個參數(shù)是time,第二個參數(shù)是dispatch_queue,第三個參數(shù)是要執(zhí)行的block
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"dispatch_after");
    });

由于其內(nèi)部使用的是dispatch_time_t管理時(shí)間,而不是NSTimer。
所以如果在子線程中調(diào)用,相比performSelector:afterDelay,不用關(guān)心runloop是否開啟

七、使用dispatch_once實(shí)現(xiàn)單例

+ (instancetype)shareInstance {

    static dispatch_once_t onceToken;

    static id instance = nil;

    dispatch_once(&onceToken, ^{

        instance = [[self alloc] init];
    });

    return instance;
}
?著作權(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)容

  • GCD筆記 總結(jié)一下多線程部分,最強(qiáng)大的無疑是GCD,那么先從這一塊部分講起. Dispatch Queue的種類...
    jins_1990閱讀 834評論 0 1
  • 前言 iOS-GCD原理分析(一)[http://www.itdecent.cn/p/9906e56ee849]...
    似水流年_9ebe閱讀 1,255評論 0 4
  • 一、死鎖場景: 主線程調(diào)用主線程。 原因:從控制臺輸出可以看出,任務(wù)2和任務(wù)3沒有執(zhí)行,此時(shí)已經(jīng)死鎖了。因?yàn)閐is...
    愛恨的潮汐閱讀 3,744評論 0 8
  • 一、GCD的API 1. Dispatch queue 在執(zhí)行處理時(shí)存在兩種Dispatch queue: 等待現(xiàn)...
    doudo閱讀 589評論 0 0
  • 技 術(shù) 文 章 / 超 人 GCD 介紹Grand Central Dispatch(GCD) 是Apple開發(fā)的...
    樹下敲代碼的超人閱讀 786評論 0 12

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