? ? ? ? 最近頗花了一番功夫把多線程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)!!!