GCD
隊列
- 串行隊列
- 并行隊列
- 全局隊列
- 主隊列
幾個容易混淆的概念
dispatch_barrier_async柵欄函數
dispatch_group_t隊列組
dispatch_semaphore_t信號量
dispatch_apply 快速迭代
dispatch_suspend和dispatch_resume
例:
GCD:
GCD全稱 Grand Central Dispatch,我們通俗的翻譯叫牛逼的中心調度。
隊列
先說下GCD中的隊列 dispatch_queue_t
串行隊列: 同步執行,在當前線程執行
并行隊列: 可由多個線程異步執行,但任務的取出還是FIFO的
//創建隊列
//參數1: 隊列名
//參數2: 隊列類型 DISPATCH_QUEUE_SERIAL | NULL 串行隊列,
DISPATCH_QUEUE_CONCURRENT 并發隊列
dispatch_queue_t sky_queue = dispatch_queue_create("隊列名字", NULL);
全局隊列: 顧名思義就是全局隊列,由系統創建,屬于并行隊列
//全局隊列
/*
參數1 : iOS8以前是優先級, iOS8以后是服務質量
iOS8以前
* - DISPATCH_QUEUE_PRIORITY_HIGH 高優先級 2
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: 默認的優先級 0
* - DISPATCH_QUEUE_PRIORITY_LOW: 低優先級 -2
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND:
iOS8以后
* - QOS_CLASS_USER_INTERACTIVE 0x21 用戶交互(用戶迫切想執行任務)
* - QOS_CLASS_USER_INITIATED 0x19 用戶需要
* - QOS_CLASS_DEFAULT 0x15 默認
* - QOS_CLASS_UTILITY 0x11 工具(低優先級, 蘋果推薦將耗時操作放到這種類型的隊列中)
* - QOS_CLASS_BACKGROUND 0x09 后臺
* - QOS_CLASS_UNSPECIFIED 0x00 沒有設置
參數2: 沒用過一般傳 0
*/
//例子
dispatch_queue_t sky_global_queue = dispatch_get_global_queue(0, 0);
主隊列: 主隊列屬于串行隊列. 運行在主線程(UI線程)
//主隊列
dispatch_queue_t sky_main_queue = dispatch_get_main_queue();
幾個容易混淆的概念
- **串行隊列,同步執行: ** 在當前線程上順序執行任務,并不會開啟新線程.
- 串行隊列,異步執行: 在不是主線程的另一個線程上順序執行任務.(因為是異步執行,如果多線程的話不能保證順序執行,所以是一個線程。)
- 并發隊列,同步執行 : 在當前線程上順序執行任務,并不會開啟新線程. (同串行隊列同步執行)
- 并發隊列,異步執行: 給每一個任務開啟一個線程去執行(因為隊列中的所有任務都是異步的)分別執行每個任務.每個任務都是從頭開始的.哪個任務先結束由任務本身決定,但是系統都會給每一個任務一個單獨的線程去執行。
看看代碼:
//串行 同步
- (void)sky_syncSerial
{
//同步+串行 不會開始新線程. 等到同步函數調用完成之后才會執行后面的代碼
dispatch_queue_t sky_sync_serial_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_SERIAL);// DISPATCH_QUEUE_SERIAL == NULL
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
運行結果:
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 1 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 2 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 3 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.425 GCDNotes[93061:9398100] 4
*/
}
//串行 異步
- (void)sky_asyncSerial
{
//在不是主線程的另一個線程順序執行任務. 因為是異步所以開啟新線程. 因為是串行所以是一個線程.
dispatch_queue_t sky_global_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(sky_global_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_global_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_async(sky_global_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
運行結果:
2017-01-26 18:56:19.132 GCDNotes[93256:9704465] 4
2017-01-26 18:56:19.132 GCDNotes[93256:9704622] 1 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
2017-01-26 18:56:19.133 GCDNotes[93256:9704622] 2 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
2017-01-26 18:56:19.133 GCDNotes[93256:9704622] 3 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
*/
}
//并發 同步
- (void)sky_syncConCurrent
{
//同步 + 并發 : 在同一個線程里面.但不是主線程,如果同步在主線程里面會造成死鎖.
//原因: 主隊列,如果主線程正在執行代碼,就不調度任務;同步執行:一直執行第一個任務直到結束。兩者互相等待造成死鎖
//錯誤代碼示例:
/*
dispatch_queue_t sky_main_queue = dispatch_get_main_queue();
dispatch_sync(sky_main_queue, ^{
//發送錯誤,不會走下面的代碼 ~
NSLog(@"erroe = %@",sky_main_queue);
});
*/
dispatch_queue_t sky_sync_Concurrent_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
運行結果:
2017-01-26 19:01:34.868 GCDNotes[93272:9727007] 1 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 2 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 3 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 4
*/
}
//并發 異步
- (void)sky_asyncConCurrent
{
//開啟新線程. 有多少任務開啟多少線程.(如果任務完成,其它的任務就會使用已經完成的.沒有完成就會繼續開啟線程)
dispatch_queue_t sky_async_concurrent_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"4 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"5 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"6 = %@",[NSThread currentThread]);
});
NSLog(@"7");
/*
運行結果:
2017-01-26 19:07:04.372 GCDNotes[93315:9751108] 3 = <NSThread: 0x6000002638c0>{number = 5, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751058] 7
2017-01-26 19:07:04.372 GCDNotes[93315:9751129] 2 = <NSThread: 0x60000007a600>{number = 4, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751107] 1 = <NSThread: 0x608000074940>{number = 3, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751110] 4 = <NSThread: 0x600000264300>{number = 6, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751394] 5 = <NSThread: 0x600000264380>{number = 7, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751108] 6 = <NSThread: 0x6000002638c0>{number = 5, name = (null)}
*/
}
dispatch_barrier_async柵欄函數
barrier_async
只有前面的任務執行并完成后才會執行. 如果后面還有任務要等到barrier_async
執行完成之后才會繼續執行.
- 必須使用自定義的隊列.否則即使加上也和全局隊列效果一樣,也就是在全局隊列中
barrier_async
會和其它普通的任務一樣.不會等到他之前的所有任務執行完成之后再執行. -
dispatch_barrier_sync
會在主線程中執行任務。
dispatch_queue_t sky_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(sky_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_barrier_async(sky_queue, ^{
NSLog(@"barrier %@ ",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"4 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"5 = %@",[NSThread currentThread]);
});
/**
運行結果:
2017-01-26 19:42:09.012 GCDNotes[93364:9892792] 2 = <NSThread: 0x60800026d1c0>{number = 4, name = (null)}
2017-01-26 19:42:09.012 GCDNotes[93364:9892793] 1 = <NSThread: 0x600000076b40>{number = 3, name = (null)}
2017-01-26 19:42:09.012 GCDNotes[93364:9892793] barrier <NSThread: 0x600000076b40>{number = 3, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892792] 3 = <NSThread: 0x60800026d1c0>{number = 4, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892795] 4 = <NSThread: 0x608000276180>{number = 5, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892793] 5 = <NSThread: 0x600000076b40>{number = 3, name = (null)}
*/
dispatch_group_t隊列組
- 使用場景: 異步執行2個操作,等都執行完成之后,回到主線程繼續執行.(比如倆圖片下載完后合并成一個,或倆網絡請求完成之后去刷新列表等~(柵欄函數也可以 ))
- 設置阻塞組(超時)任務一定時間.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第一個參數是需要阻塞的組,第二個是阻塞的時間. -
dispatch_group_notify
當group關聯的block執行完畢后,就調用這個block
系統管理隊列組和手動管理隊列組. 兩個是等價的. 手動的enter和leave必須成對使用.
//系統管理
dispatch_group_async(group, queue, ^{
//...
});
//手動管理
dispatch_group_enter(group);
dispatch_async(queue, ^{
// ...
dispatch_group_leave(group);
});
//線程隊列組
dispatch_queue_t sky_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t sky_group = dispatch_group_create();
//線程組的用法和原理:可以給一個任務組設置一個阻塞(超時)時間,如
果給一個任務組的前兩個任務設置一個超時時間那么如果這前兩個任務
提前執行結束了,也就是在設定的阻塞時間內完成了任務,那么這個阻塞
就會放行下面的任務去執行任務,如果超過了超時時間那么這個阻塞也
會放行,讓下面的任務可以執行.阻塞時間并不能殺死任務,也就是說如果
你設定了阻塞(超時)時間,阻塞時間前的任務在阻塞放行前并沒有執行完
成,然后任務就被放行了,系統繼續往下執行,但是這時候阻塞時間前的任
務也會執行完成如果你不手動殺死它的話.
//dispatch_group_wait(sky_group, 3); //第一個參數 阻塞的組 第二個
參數 阻塞時間
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 1 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 2 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
[NSThread sleepForTimeInterval:3];//等待三秒
NSLog(@" 3 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 4 %@ ",[NSThread currentThread]);
});
//回到主線程
dispatch_group_notify(sky_group, sky_queue, ^{
//監測方法在最后一個任務執行的線程中執行
NSLog(@"回到主線程了 ~ %@" , [NSThread currentThread]);
});
/**
運行結果:
2017-01-26 20:26:27.768 GCDNotes[93449:10087233] 1 <NSThread: 0x60000026b6c0>{number = 4, name = (null)}
2017-01-26 20:26:27.768 GCDNotes[93449:10087235] 2 <NSThread: 0x600000260f40>{number = 3, name = (null)}
2017-01-26 20:26:27.768 GCDNotes[93449:10087330] 4 <NSThread: 0x600000271cc0>{number = 5, name = (null)}
2017-01-26 20:26:30.769 GCDNotes[93449:10087232] 3 <NSThread: 0x608000260f80>{number = 6, name = (null)}
2017-01-26 20:26:30.770 GCDNotes[93449:10087232] 回到主線程了 ~ <NSThread: 0x608000260f80>{number = 6, name = (null)}
*/
dispatch_semaphore_t信號量
信號量是用于控制并發數量的,所以只用在全局隊列和并行隊列中.
- 創建信號量,可以設置信號量的資源數。0表示沒有資源,調用
dispatch_semaphore_wait
會立即等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- 等待信號,可以設置超時參數。該函數返回0表示得到通知,非0表示超時。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- 通知信號,如果等待線程被喚醒則返回非0,否則返回0。
dispatch_semaphore_signal(semaphore);
//創建隊列組
dispatch_group_t sky_group = dispatch_group_create();
//創建信號為3的信號量
dispatch_semaphore_t sky_semaphore = dispatch_semaphore_create(3);
dispatch_queue_t sky_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0 ; i < 10 ; i ++)
{
//設置等待信號 DISPATCH_TIME_FOREVER(永遠) | DISPATCH_TIME_NOW(現在)
dispatch_semaphore_wait(sky_semaphore, DISPATCH_TIME_FOREVER);
//
dispatch_group_async(sky_group, sky_global, ^{
NSLog(@"i = %d : %@",i , [NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
//設置通知信號
dispatch_semaphore_signal(sky_semaphore);
});
}
//通知主線程
dispatch_group_notify(sky_group, sky_global, ^{
NSLog(@"xxxxoooo");
});
/*
運行結果:
2017-01-26 21:10:53.006 GCDNotes[93525:10184012] i = 2 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:53.006 GCDNotes[93525:10184037] i = 0 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:53.006 GCDNotes[93525:10184011] i = 1 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184037] i = 4 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184012] i = 3 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184011] i = 5 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184037] i = 8 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184012] i = 6 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184011] i = 7 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:56.020 GCDNotes[93525:10184037] i = 9 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:57.023 GCDNotes[93525:10184037] xxxxoooo
*/
//結果分析: 來自: http://www.lxweimin.com/p/ac11fe7ef78c
/*
首先這里面創建了一個組,這個組放在這里只是告訴你可以這么用,它不會影響信號量的功能.
這里創建了一個并發為3的信號量然而for循環是10個任務,那么理論
上10個任務會創建十個線程去執行,但是你信號量為3所以只能創建3
個線程去執行前面10個任務,然后等待前3個任務執行完成了騰出來新
的線程再去執行等待執行的.所以下面的線程 number 最大是4,當然這
前提是你設置的超時時間大于任務執行完的時間.如果設置超時時間是
0.1秒的話,任務等超時了還是會開啟另外的線程去執行任務,這樣就
達不到控制并發量的要求了.
*/
//理解信號量: 來自: http://www.lxweimin.com/p/ac11fe7ef78c
/*
一般可以用停車來比喻:
停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此
時來了五輛車,那么就有一輛需要等待。信號量的值就相當于剩余車位
的數目,dispatch_semaphore_wait函數就相當于來了一輛車,
dispatch_semaphore_signal就相當于走了一輛車。停車位的剩余數目
在初始化的時候就已經指明了(dispatch_semaphore_create(long
value)),調用一次dispatch_semaphore_signal,剩余的車位就增加
一個;調用一次dispatch_semaphore_wait剩余車位就減少一個;當剩
余車位為0時,再來車(即調用dispatch_semaphore_wait)就只能等
待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己
設定了一段等待時間,這段時間內等不到停車位就走了,如果等到了就
開進去停車。而有些車主就像把車停在這,所以就一直等下去。
*/
dispatch_apply 快速迭代
可以給定指定的次數將block追加到指定的Dispatch Queue中,并且等待全部結束處理執行。
dispatch_queue_t sky_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
NSArray *array = @[@"北京",@"我愛你",@"濟南大學的遺憾",@"在濟南大學的操場望天空",@"濟南大學的食堂",@"夢游濟南大學"];
//參數1: 要遍歷的次數
//參數2: 在那個線程中
//參數3: 回調
dispatch_apply(array.count, sky_queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
NSLog(@"xxxooo");
/*
運行結果:
2017-01-26 21:48:46.007 GCDNotes[93665:10365139] 在濟南大學的操場望天空
2017-01-26 21:48:46.007 GCDNotes[93665:10365141] 我愛你
2017-01-26 21:48:46.007 GCDNotes[93665:10365138] 濟南大學的遺憾
2017-01-26 21:48:46.007 GCDNotes[93665:10364896] 北京
2017-01-26 21:48:46.007 GCDNotes[93665:10365139] 濟南大學的食堂
2017-01-26 21:48:46.007 GCDNotes[93665:10365141] 夢游濟南大學
2017-01-26 21:48:46.007 GCDNotes[93665:10364896] xxxooo
*/
//無序.
dispatch_suspend和dispatch_resume
NSOperationQueue
有暫停(suspend)和恢復(resume)。其實GCD中的隊列也有類似的功能。用法也非常簡單:
dispatch_suspend(queue)
//暫停某個隊列
dispatch_resume(queue)
//恢復某個隊列
這些函數不會影響到隊列中已經執行的任務,隊列暫停后,已經添加到隊列中但還沒有執行的任務不會執行,直到隊列被恢復。
例:
- 多線程中下載圖片,回到主線程刷新UI
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//多線程執行 ~
NSURL *imageUrl = [NSURL URLWithString:@"http://b.hiphotos.baidu.com/image/pic/item/6a63f6246b600c33fe5527171e4c510fd8f9a1c5.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = [UIImage imageWithData:imageData];
/*
注意:
如果想等UI更新完畢再執行后面的代碼, 那么使用同步函數
如果不想等UI更新完畢就需要執行后面的代碼, 那么使用異步函數
*/
dispatch_sync(dispatch_get_main_queue(), ^{
//同步回到主線程刷新ui
self.imageView.image = image;
});
});
- GCD 延遲
//延遲1秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
//這里傳入全局隊列會在子線程中執行block,如果傳入主隊列就會在主線程中執行block
// ...
});
- 一次執行(一般用來實現單列和全局就調用一次代碼的地方比如:時間計算那塊.特別是在復雜的列表頁面每次都去計算時間格式很消耗性能.加上這個滑動就會流暢很多.)
//一次執行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});