多線程GCD.

? ? ? ? 最近頗花了一番功夫把多線程GCD人的一些用法總結(jié)出來,一來幫自己鞏固一下知識、二來希望能幫到對這一塊還迷茫的同學(xué)。

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

一、任務(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ù)往下運行。

如果是異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。

隊列:用于存放任務(wù)。一共有兩種隊列,串行隊列和并行隊列。

串行隊列:隊列中的任務(wù),GCD會FIFO(先進先出)地取出來一個,執(zhí)行一個,然后取下一個,這樣一個一個的執(zhí)行。

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

1.0 創(chuàng)建和獲得隊列

1.1 主隊列:這是一個特殊的串行隊列。什么是主隊列,大家都知道吧,它用于刷新UI,任何需要刷新UI的工作都要在主隊列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。

dispatch_queue_t queue = dispatch_get_main_queue();

1.2 自己創(chuàng)建的隊列:其中第一個參數(shù)是標識符,用于DEBUG的時候標識唯一的隊列,可以為空。大家可以看xcode的文檔查看參數(shù)意義。第二個參數(shù)用來表示創(chuàng)建的隊列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL或NULL表示創(chuàng)建串行隊列。傳入DISPATCH_QUEUE_CONCURRENT表示創(chuàng)建并行隊列。

串行隊列

dispatch_queue_t queue1 = dispatch_queue_create("tk.bourne.testQueue", NULL);

dispatch_queue_t queue2 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

并行隊列

dispatch_queue_t queue3 = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

1.3 全局并行隊列:只要是并行任務(wù)一般都加入到這個隊列。這是系統(tǒng)提供的一個并發(fā)隊列。

第一個參數(shù)線程優(yōu)先級

第二個參數(shù)0 :占位符、目前沒有意義

dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.4 Global Dispatch Queue(高優(yōu)先級)的獲取方法

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

1.5 Global Dispatch Queue(默認優(yōu)先級)的獲取方法

dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

1.6 Global Dispatch Queue(低優(yōu)先級)的獲取方法

dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

1.7 Global Dispatch Queue(后臺優(yōu)先級)的獲取方法

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

2.0 創(chuàng)建任務(wù)

同步任務(wù):會阻塞當(dāng)前線程(SYNC)

dispatch_sync(queue1, ^{

NSLog(@"%@", [NSThread currentThread]);

});

異步任務(wù):不會阻塞當(dāng)前線程(ASYNC)

dispatch_async(queue3, ^{

NSLog(@"%@", [NSThread currentThread]);

});

二、隊列組

? ? ? ? 隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當(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(NSIntegeri =0; i <3; i++) {

NSLog(@"group-01 - %@", [NSThreadcurrentThread]);

}

});

3.2.主隊列執(zhí)行8次循環(huán)

dispatch_group_async(group,dispatch_get_main_queue(), ^{

for(NSIntegeri =0; i <8; i++) {

NSLog(@"group-02 - %@", [NSThreadcurrentThread]);

}

});

3.3.執(zhí)行5次循環(huán)

dispatch_group_async(group, queue, ^{

for(NSIntegeri =0; i <5; i++) {

NSLog(@"group-03 - %@", [NSThreadcurrentThread]);

}

});

4.都完成后會自動通知

dispatch_group_notify(group,dispatch_get_main_queue(), ^{

NSLog(@"完成- %@", [NSThreadcurrentThread]);

});

三、dispatch_barrier_sync

為了高效率地進行訪問,讀取處理追加到Concurrent Dispathc Queue中,寫入處理在任一個讀取處理沒有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中即可(在寫入處理結(jié)束之前,讀取處理不可執(zhí)行)。

雖然利用Dispatch Group和dispatch_set_target_queue函數(shù)也可實現(xiàn),但是源碼會很復(fù)雜。GCD為我們提供了高效的dispatch_barrier_sync函數(shù)。該函數(shù)同dispatch_queue_create函數(shù)生成的Concurrent Dispathc Queue一起使用。

dispatch_queue_tqueue =dispatch_queue_create("id=929",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:6];

NSLog(@"reading01");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:3];

NSLog(@"reading02");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:2];

NSLog(@"reading03");

});

dispatch_async(queue, ^{

[NSThreadsleepForTimeInterval:9];

NSLog(@"reading04");

});

#warning dispatch_barrier_async會監(jiān)控queue直到先加入隊列的任務(wù)執(zhí)行完才會執(zhí)行自己加入的任務(wù),并且會阻塞queue直到自己block中的任務(wù)執(zhí)行完才會讓后續(xù)任務(wù)執(zhí)行。dispatch_barrier_async函數(shù)本身是異步的不會阻塞當(dāng)前線程

dispatch_barrier_async(queue, ^{

[NSThreadsleepForTimeInterval:5];

NSLog(@"writing");

});

NSLog(@"走到dispatch_barrier_async函數(shù)下面了");

dispatch_async(queue, ^{

NSLog(@"reading05");

});

dispatch_async(queue, ^{

NSLog(@"reading06");

});

dispatch_async(queue, ^{

NSLog(@"reading07");

});

四、dispatch_set_target_queue

/**

dispatch_queue_create函數(shù)生成的Dispatch Queue不管是Serial Dispatch Queue還是Concurrent Dispatch Queue,都使用與默認優(yōu)先級Global Dispatch Queue相同執(zhí)行優(yōu)先級的線程。

*/

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("id=19", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

//變更優(yōu)先級的函數(shù)dispatch_set_target_queue

/**

參數(shù)一:要變更優(yōu)先級的Dispatch Queue

參數(shù)二:目標優(yōu)先級Dispatch Queue,與目標Dispatch Queue有相同的執(zhí)行優(yōu)先級

*/

dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

五、dispatch_after

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

/**

參數(shù)一:指定時間用的 dispatch_time_t 類型值

參數(shù)二:在哪個隊列執(zhí)行

參數(shù)三:要執(zhí)行的任務(wù)

*/

dispatch_after(time, dispatch_get_main_queue(), ^{

NSLog(@"waited at least three seconds.");

});

#warning dispatch_after函數(shù)并不是指定延遲后執(zhí)行block里的任務(wù),而是指定時間后把任務(wù)加進隊列

六、dispatch_apply

/**

dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關(guān)聯(lián)API。該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等待全部處理執(zhí)行結(jié)束。

*/

/**

參數(shù)一: 重復(fù)次數(shù)

參數(shù)二: 追加對象的Dispatch Queue

參數(shù)三: 追加的處理

與目前為止所出現(xiàn)的例子不同,第三個參數(shù)的Block為帶有Block的參數(shù)。這是為了按第一個參數(shù)重復(fù)追加Block并區(qū)分各個Block而使用。

*/

//? ? dispatch_apply(10, queue, ^(size_t index) {

//? ? ? ? NSLog(@"%zu",index);

//? ? });

//#warning dispatch_apply會阻塞當(dāng)前線程直到queue中新添加的10次任務(wù)執(zhí)行結(jié)束才會取消阻塞。要想10次任務(wù)依次執(zhí)行可以把queue換成一個串行隊列

//? ? NSLog(@"done");

/* 例如要對NSArray類對象的所有元素執(zhí)行處理時,不必一個一個編寫for循環(huán)部分。*/

//? ? NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

//? ? dispatch_apply([array count], queue, ^(size_t index) {

//? ? ? ? NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

//? ? });

/* 由于dispatch_apply函數(shù)也與dispatch_sync函數(shù)相同,會等待處理執(zhí)行結(jié)束,因此推薦在dispatch_async函數(shù)中非同步地執(zhí)行dispatch_apply函數(shù) */

NSArray *array = [NSArray arrayWithObjects:@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11, nil];

dispatch_async(queue, ^{

//Global Dispatch Queue 等待dispatch_appply函數(shù)中全部處理執(zhí)行結(jié)束

dispatch_apply([array count], queue, ^(size_t index) {

//并列處理包含在NSArray對象的全部對象

NSLog(@"%zu: %@", index, [array objectAtIndex:index]);

});

//dispatch_apply函數(shù)中的處理全部執(zhí)行結(jié)束

/* 在Main Dispatch Queue中非同步執(zhí)行 */

dispatch_async(dispatch_get_main_queue(), ^{

/*

在Main Dispatch Queue中執(zhí)行處理

用戶界面更新等

*/

NSLog(@"done");

});

});

七、dispatch_suspend / dispatch_resume

/**

當(dāng)追加大量處理到Dispatch Queue時,在追加處理過程中,有時希望不執(zhí)行已經(jīng)追加的處理。例如演算結(jié)果被Block截獲時,一些處理會對這個演算結(jié)果造成影響。

在這種情況下,只要掛起Dispatch Queue即可。當(dāng)可以執(zhí)行時再恢復(fù)。

*/

dispatch_queue_t queue1 = dispatch_queue_create("id = 88", DISPATCH_QUEUE_CONCURRENT);

//dispatch_suspend函數(shù)掛起指定的Dispatch Queue

dispatch_suspend(queue1);

//dispatch_resume函數(shù)恢復(fù)指定的Dispatch Queue

dispatch_resume(queue1);

#warning 這些函數(shù)對已經(jīng)執(zhí)行的處理沒有影響。掛起后,追加到Dispatch Queue中尚未執(zhí)行的處理在此之后停止執(zhí)行。而恢復(fù)則使得這些處理能夠繼續(xù)執(zhí)行。

八、Dispatch Semaphore

/**

當(dāng)并行執(zhí)行的處理更新數(shù)據(jù)時,會產(chǎn)生數(shù)據(jù)不一致的情況,有時應(yīng)用程序還會異常結(jié)束。雖然使用Serial Dispatch Queue和dispatch_barrier_async函數(shù)可避免這類問題,但有必要進行更細粒度的排他控制。

*/

#pragma mark - 出錯的代碼

//? ? NSMutableArray *array = [[NSMutableArray alloc]init];

//? ? for (int i = 0; i < 100000; i++) {

//? ? ? ? dispatch_async(queue, ^{

//? ? ? ? ? ? [array addObject:[NSNumber numberWithInt:i]];

//? ? ? ? });

//? ? }

/*因為該源代碼使用Global Dispatch Queue更新NSMutableArray類對象,所在執(zhí)行后由于內(nèi)存錯誤導(dǎo)致應(yīng)用程序結(jié)束的概率很高。此時應(yīng)使用Dispatch Semaphore。

Dispatch Semaphore是持有計數(shù)的信號,該計數(shù)是多線程編程中的計數(shù)類型信號。計數(shù)為0時等待,計數(shù)為1或大于1時,減1而不等待。

*/

//? ? //設(shè)置參數(shù)為1

//? ? dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

//? ? //設(shè)置超是等待,即最長等待時間

//? ? dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

//? ? /**

//? ? 參數(shù)一: dispatch_semaphore_t對象

//? ? 參數(shù)二: 最長等待時間,與dispatch_group_wait中的超時等待是一樣的

//? ? */

//? ? long result = dispatch_semaphore_wait(semaphore, time);

//? ? if (result == 0) {

//

//? ? }else{

//

//? ? }

/** 用Dispatch Semaphore改寫出錯的代碼

生成Dispatch Semaphore

Dispatch Semaphore的計數(shù)初始值設(shè)定為1

保證可訪問NSMutableArray類對象的線程同時只能有一個。

*/

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc]init];

for (int i = 0; i < 100000; i++) {

dispatch_async(queue, ^{

/**

等待Dispatch Semaphore

一直等待,直到Dispatch Semaphore的計數(shù)值達到大于等于1

*/

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

/**

由于Dispatch Semaphore的計數(shù)值達到或大于等于1

所以將Dispatch Semaphore的計數(shù)值減去1

dispatch_semaphore_wait函數(shù)執(zhí)行返回

即執(zhí)行到此時的Dispatch Semaphore的計數(shù)恒為0

由于可訪問NSMutableArray類對象的線程只有1個

因此可安全地進行更新

*/

[array addObject:[NSNumber numberWithInt:i]];

NSLog(@"%d",i);

/**

排他控制處理結(jié)束,

所以通過dispatch_semaphore_signal函數(shù)將Dispatch Semaphore的計數(shù)值加1

如果有通過dispatch_semaphore_wait函數(shù)

等待Dispatch Semaphore的計數(shù)值增加的線程,

就由最先等待的線程執(zhí)行。

*/

dispatch_semaphore_signal(semaphore);

});

}

九、dispatch_once

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

static int initialized = NO;

if (initialized == NO) {

//初始化

initialized = YES;

}

#warning 上面的源代碼在大多數(shù)情況下也是安全的,但是在多核CPU中,在正在更新表示是否初始化的標志變量時讀取,就有可能多次執(zhí)行初始化處理。而dispatch_once函數(shù)就沒有這個問題,通過dispatch_once函數(shù)生成的代碼即使在多線程環(huán)境下執(zhí)行,也可保證百分百安全。

/* 上面的代碼可以簡化 */

static dispatch_once_t pred;

dispatch_once(&pred, ^{

//初始化

});

-----------------------我是無趣的分割線---------------------

上面這么枯躁的東西都能堅持看完,到此已經(jīng)證明你有編程的資質(zhì)!祝你編程之路一路順風(fēng)!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • #import "ViewController.h" @interface ViewController () @...
    艾克12138閱讀 225評論 0 0
  • 目錄 一、基本概念1.多線程2.串行和并行, 并發(fā)3.隊列與任務(wù)4.同步與異步5.線程狀態(tài)6.多線程方案 二、GC...
    BohrIsLay閱讀 1,612評論 5 12
  • 基于C接口的線程調(diào)度。 dispatch_queue_attr_t : 定義一個queue的時候,用于指定queu...
    lzh_coder閱讀 988評論 0 0
  • 前言 GCD是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的...
    進無盡閱讀 253評論 0 1
  • 一、簡介在iOS所有實現(xiàn)多線程的方案中,GCD應(yīng)該是最有魅力的,因為GCD本身是蘋果公司為多核的并行運算提出的解決...
    MYS_iOS_8801閱讀 577評論 0 0