把GCD的資料重新整理了一下 ( ̄. ̄)
GCD全稱Grand Central Dispatch
是Apple開發(fā)的一個多核編程的較新的解決方法。
它主要用于優(yōu)化應用程序以支持多核處理器以及其他對稱多處理系統(tǒng)。
它是一個在線程池模式的基礎上執(zhí)行的并行任務。
純C語言 提供了非常多強大的函數(shù)
在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
設計
GCD是蘋果公司為多核并行運算提出的解決方案
GCD會自動管理線程的生命周期(創(chuàng)建線程、調度任務、銷毀線程)
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD是一個替代諸如NSThread等技術的很高效和強大的技術。
GCD完全可以處理諸如數(shù)據(jù)鎖定和資源泄漏等復雜的異步編程問題。
GCD的工作原理是讓一個程序,根據(jù)可用的處理資源,安排他們在任何可用的處理器核心上平行排隊執(zhí)行特定的任務。這個任務可以是一個功能或者一個程序段。
GCD創(chuàng)建的隊列是輕量級的,蘋果聲明一個GCD的工作單元需要由15個指令組成。也就是說創(chuàng)造一個傳統(tǒng)的線程很容易的就會需要幾百條指令。
GCD中的一個任務可被用于創(chuàng)造一個被放置于隊列的工作項目或者事件源。如果一個任務被分配到一個事件源,那么一個由功能或者程序塊組成的工作單元會被放置于一個適當?shù)年犃兄?。蘋果公司認為GCD相比于普通的一個接一個的執(zhí)行任務的方式更為有效率。
任務(block ):要執(zhí)行的操作
任務是由block封裝的
dispatch_block_t task
void(^myBlock)()=^{
//任務/想要做的事情
}
隊列(queue): 用來存放任務
隊列不是線程 隊列中存放的任務最后都要由線程來執(zhí)行
隊列的原則:(FIFO) First In First Out 先進先出 后進后出
隊列類型
串行隊列:Serial Dispatch Queue
存放順序執(zhí)行的任務
線程池只提供一個線程用來執(zhí)行任務
一個任務執(zhí)行完畢 在執(zhí)行下一個任務
并發(fā)隊列:Concurrent Dispatch Queue
存放想要同時并發(fā)執(zhí)行的任務 可以開啟多線程 具體數(shù)量由底層GCD負責
線程池可以提供多個線程來執(zhí)行任務 具體數(shù)量由底層GCD負責
?
我以前在這里打了個問號 回頭卻忘了有什么疑問 銘記教訓 以后 有疑問 就把自己的疑問寫清楚
并發(fā)執(zhí)行 性能高 執(zhí)行順序不固定 費電因為絕大多數(shù)會使用全局隊列,全局隊列本身就是并發(fā)隊列
dispatch_queue_create
//串行隊列
dispatch_queue_t SerialQueue;
//并發(fā)隊列
dispatch_queue_t ConcurrentQueue;
//后面這個參數(shù)可以不寫的 默認填NULL就是串行
SerialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
ConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
系統(tǒng)隊列
系統(tǒng)提供了兩個隊列
** 主隊列**:dispatch_get_main_queue
屬于串行隊列
負責調度主線程度的任務,沒有辦法開辟新的線程。
所以主隊列下的任務不管是異步任務還是同步任務都不會開辟線程
任務只會在主線程順序執(zhí)行。
主隊列異步任務:現(xiàn)將任務放在主隊列中,但是不是馬上執(zhí)行,等到主隊列中的其它所有除我們使用代碼添加到主隊列的任務的任務都執(zhí)行完畢之后才會執(zhí)行我們使用代碼添加的任務。
主隊列同步任務:容易阻塞主線程,所以不要這樣寫。原因:我們自己代碼任務需要馬上執(zhí)行,但是主線程正在執(zhí)行代碼任務的方法體,因此代碼任務就必須等待,而主線程又在等待代碼任務的完成好去完成下面的任務,因此就形成了相互等待。整個主線程就被阻塞了。
全局隊列: dispatch_get_global_queue
屬于并發(fā)隊列
一般情況下 并發(fā)任務都可以放在全局并發(fā)隊列中
全局隊列和并發(fā)隊列的區(qū)別:
1 全局隊列沒有名字,但是并發(fā)隊列有名字。有名字可以便于查看系統(tǒng)日志
2 全局隊列是所有應用程序共享的。
3 在mrc的時候,全局隊列不用手動釋放,但是并發(fā)隊列需要。
dispatch_queue_t
dispatch_queue_t mymainQueue;
dispatch_queue_t myglobalQueue;
mymainQueue = dispatch_get_main_queue();
myglobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**參數(shù)說明:
參數(shù)1:代表該任務的優(yōu)先級,默認寫0就行,不要使用系統(tǒng)提供的枚舉類型,因為ios7和ios8的枚舉數(shù)值不一樣,使用數(shù)字可以通用。
DISPATCH_QUEUE_PRIORITY_HIGH 2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND (-32768)
參數(shù)2:蘋果保留關鍵字,一般寫0或NULL
所以也可以寫為myglobalQueue = dispatch_get_global_queue(0, 0);
*/
同步異步
sync同步運行:
如果是同步執(zhí)行 隊列會等任務結束后 再調度后續(xù)的任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
async 異步運行:
dispatch_sync(dispatch_queue_t queue, ^(void)block )
*dispatch_block_t 就是無返回值 無參數(shù)的block
常用用法介紹
1.經典用法(子線程下載(耗時操作),主線程刷新UI)
dispatch_async(dispatch_get_global_queue(0,0), ^{
//執(zhí)行耗時的異步操作…
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程,執(zhí)行UI刷新操作
});
});
2.GCD的延時執(zhí)行
延時是延時任務加入到隊列的時間 不是延時任務執(zhí)行的時間
//1.延時不是一定時間后執(zhí)行相應的任務,而是一定時間后,將任務加入到隊列中(隊列里面再分配執(zhí)行的時間)
//2.主線程 RunLoop 1/60秒檢測時間,追加的時間范圍 3s~(3+1/60)s
//3.在哪個線程執(zhí)行,跟隊列類型有關
//dispatch_after(一定時間后,將執(zhí)行的操作加入到隊列中)
//dispatch_time_t when 指定時間
/* NSEC_PER_SEC 秒
* NSEC_PER_MSEC 毫秒
* NSEC_PER_USEC 微秒
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_queue_t que = dispatch_queue_create("h", DISPATCH_QUEUE_SERIAL);
//1.第一種用法
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"第一種延時 code to be executed on the main queue after delay");
});
//2.第二種用法
//dispatch_function_t work 執(zhí)行的c語言方法
dispatch_after_f(time, que, NULL, fun1);
//3.第三種用法
dispatch_after(time, que, ^{
NSLog(@"第三種延時 code to be executed on the main queue after delay");
});
------代表方法外的分割-----
void fun1(){
NSLog(@"第二種延時 code to be executed on the main queue after delay");
}
3.異步執(zhí)行:
//dispatch_async +全局并發(fā)隊列(可以開啟多條線程)
//dispatch_async +自己創(chuàng)建的串行隊列(開啟一條線程)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
dispatch_async(dispatch_get_main_queue(), ^{
// 主隊列異步
});
一次性執(zhí)行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
4.一次性執(zhí)行和多次執(zhí)行
/*
一次執(zhí)行:dispatch_once
作用:在多線程的情況下,同樣能夠保證指定的代碼塊只被執(zhí)行一次
快捷鍵:
應用場景:單例設計模式
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"塊代碼只能執(zhí)行一次");
});
/*
多次執(zhí)行:dispatch_apply
以指定的次數(shù)將指定的Block加入到指定的隊列中 并等待隊列中操作全部完成.
當指定隊列為串行時 有序單線程執(zhí)行
當指定隊列為并發(fā)隊列時 多線程無序執(zhí)行
*/
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_CONCURRENT);/
dispatch_apply(3, q1, ^(size_t index) {
NSLog(@"重要的事情說三遍 第%zu遍 %@",index,[NSThread currentThread]);
});
**5.dispatch_group分組 **
/**
作用:所有任務執(zhí)行完成之后,統(tǒng)一通知用戶
可以實現(xiàn)監(jiān)聽一組任務是否完成 完成后得到通知執(zhí)行其他的操作
不包括延時任務 因為延時是任務放進隊列的時間
*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_SERIAL);
dispatch_block_t t1 = ^{
NSLog(@"任務1");
};
dispatch_async(q1, t1);
dispatch_group_async(group, q1, ^{
NSLog(@"group1");
});
dispatch_group_async(group, q1, ^{
NSLog(@"group2");
});
dispatch_group_notify(group, q1, ^{
NSLog(@"end");
});
6.dispatch_barrier_async的使用
/*
dispatch_barrier_async是在前面的任務執(zhí)行結束后它才執(zhí)行,而且它后面的任務等它執(zhí)行完成之后才會執(zhí)行
*/
dispatch_queue_t queue = dispatch_queue_create("aa", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async1");
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_barrier_async2");
});
dispatch_async(queue, ^{
NSLog(@"dispatch_async3");
});
7、dispatch_set_target_queue
使用dispatch_set_target_queue將多個串行的queue指定到了同一目標,那么著多個串行queue在目標queue上就是同步執(zhí)行的,不再是并行執(zhí)行。
Important
If you modify the target queue for a queue, you must be careful to avoid creating cycles in the queue hierarchy.
dispatch_queue_t tq = dispatch_queue_create("tq",DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q2 = dispatch_queue_create("q2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q3 = dispatch_queue_create("q3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(q1, tq);
dispatch_set_target_queue(q2, tq);
dispatch_set_target_queue(q3, tq);
dispatch_async(q1, ^{
NSLog(@"1 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.f];
NSLog(@"1 out%@",[NSThread currentThread]);
});
dispatch_async(q2, ^{
NSLog(@"2 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.f];
// dispatch_suspend(queue1);
NSLog(@"2 out %@",[NSThread currentThread]);
});
dispatch_async(q3, ^{
NSLog(@"3 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.f];
NSLog(@"3 out %@",[NSThread currentThread]);
});
8.信號量 dispatch_semaphore
dispatch_semaphore
是GCD用來同步的一種方式 相關函數(shù)有三個
dispatch_semaphore_create
dispatch_semaphore_signal
dispatch_semaphore_wait
(1)dispatch_semaphore_create
dispatch_semaphore_t dispatch_semaphore_create(long value);
傳入的參數(shù)為long,輸出一個dispatch_semaphore_t
類型且值為value的信號量
*這里的傳入的參數(shù)value必須大于或等于0 否則dispatch_semaphore_create
會返回NULL。
(2)dispatch_semaphore_signal
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
這個函數(shù)會使傳入的信號量dsema的值加1
返回值為long類型
當返回值為0時表示當前并沒有線程等待其處理的信號量,其處理的信號量的值加1即可。
當返回值不為0時,表示其當前有一個或多個線程等待其處理的信號量,并且該函數(shù)喚醒了一個等待的線程(當線程有優(yōu)先級時,喚醒優(yōu)先級最高的線程;否則隨機喚醒)
。
(3) dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
這個函數(shù)會使傳入的信號量dsema的值減1;
如果dsema信號量的值大于0,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句,并且將信號量的值減1;
如果desema的值為0,那么這個函數(shù)就阻塞當前線程等待timeout(注意timeout的類型為dispatch_time_t 不能直接傳入整形或float型數(shù))
如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)(即dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1。
如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。
設置timeout時,比較有用的兩個宏:
DISPATCH_TIME_NOW 表示當前;
DISPATCH_TIME_FOREVER 表示遙遠的未來;
一般可以直接設置timeout為這兩個宏其中的一個,或者自己創(chuàng)建一個dispatch_time_t類型的變量。
返回值為long型。當其返回0時表示在timeout之前,該函數(shù)所處的線程被成功喚醒。當其返回不為0時,表示timeout發(fā)生。
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray array];
for (int index = 0; index < 10000; index++) {
dispatch_async(queue, ^(){
dispatch_semaphore_wait(semaphore, timeout);
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
dispatch_semaphore_signal(semaphore);
});
}
GCD 死鎖
-(void)viewDidLoad { [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); }
我們來一步一步解析。首先dispatch_sync 是同步的,它會造成一個后果那就是阻塞主線程,并且會一直會等待block,而block放入到了主線程隊列dispatch_get_main_queue()中,這是一個FIFA隊列,就是先進先出的隊列。他在等待主線程執(zhí)行。那么一目了然了。我用偽代碼敲出來大家就知道了
MainThread { dispatch_get_main_queue(){ syncblock(); } }
MainThread
等待dispatch_sync
,dispatch_sync
等待block
,block
等待mainquen
,maiden
等待MainThread
,而MainThread
等待dispatch_sync
。這樣就形成了一個死循環(huán)。俗稱DeadLock
死鎖。
NSLog(@"1"); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"2"); } }); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"3"); } }); NSLog(@"4");
結果大家一運行就知道,
為什么第一個只輸出1和4 和2。我們來解析問題的代碼
這里面有兩部異步block但是放到了主線程隊列里面,但是block里面執(zhí)行的是一個 while (1) 的死循環(huán)
我們一步一步解析。 主線程隊列是個FIFO 也就是先進先出,先進的完了才執(zhí)行第二個。而當
^{ while (1) { NSLog(@"2"); } }
放入到主線程隊列后,它就永遠執(zhí)行不完,永遠不會退出,所以
^{ while (1) { NSLog(@"3"); }
這個只能永遠的等待,而這兩個block又是異步的不會阻塞主線程所以主線程的輸出依然木有問題。
關于命名
所有帶t結尾的 都是用來聲明變量的對象
所有帶f結尾的 都是面向C的function
GCD官方文檔OC版
GCD學習總結
Grand Central Dispatch
SDK源碼解讀系列:《iOS與OSX多線程和內存管理》書摘之GCD內部實現(xiàn)(一)