1.GCD簡介
gcd有兩大概念:任務和隊列
(1) 任務:同步任務和異步任務。
同步任務:不會開辟線程,在當前線程執行任務
異步任務:會開辟線程,在新的線程中執行任務
(2) 隊列:串行隊列和并行隊列
串行隊列:按任務順序執行
并行隊列:并發執行
(3)任務和隊列組合
同步串行:不會開辟新的線程,在當前線程按任務順序執行(沒意義,幾乎不用)
同步并行:不會開辟新的線程,在當前線程按任務順序執行 (幾乎不用)
異步串行:會開辟一條線程,在新線程中按任務順序執行
異步并行:會開辟多個子線程,在子線程中并發執行多個任務
同步主隊列:會發生死鎖
異步主隊列:不會開辟新的線程,任務按順序執行
2.代碼解析
(一)同步主隊列(死鎖)
- (void)syncMain {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
同步:
1 ).不會開辟新線程;
2 ).上一個任務執行結束才會繼續往下執行。
結果:要想sync函數往下執行,必須等待block任務結束。
主隊列:
1 ).主隊列只能在主線程執行,不能再子線程執行;
2 ).主線程必須等待空閑的時候,才會執行下一個任務。
結果:一個線程只能執行一個任務,Block想要執行必須等待主線程空閑,而主線程在執行sync函數;所以要等待其結束才會執行。
同步主隊列:sync函數等待Block任務結束,Block任務等待sync函數結束,兩個任務互相等待,導致堵塞主線程,發生死鎖現象。
(二)異步主隊列
- (void)asyncMain {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
1.因為是異步,可以先繞過不執行,回頭再執行,所以先執行start和end
2.因為是主隊列,要在主線程中執行,所以不會開辟子線程
3.主隊列跟串行隊列一樣,任務都是按順序執行
(三)同步串行隊列
- (void)syncSerial {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
1.同步任務:不會開辟新線程
2.串行隊列:按任務順序執行
(四)同步并行隊列
- (void)syncConcurrent {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:跟同步串行一樣的道理(個人覺得同步串行和同步異行并沒有什么意義,基本上用不到)
(五)異步串行隊列
- (void)asyncSerial {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
異步任務:會開辟新的線程,可以繞過任務不執行,回頭再執行
串行隊列:任務按順序執行
異步串行:只會開辟一個新線程,任務按順序執行
(六)異步并行隊列
- (void)asyncConcurrent {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
異步任務:會開辟新線程,可以繞過任務不執行,回頭再執行
并行隊列:任務并發執行
異步并行:會開辟多個子線程,任務并發執行
(七)全局隊列
- (void)asyncGlobal {
NSLog(@"start = %@", [NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
NSLog(@"end");
}
打印結果:
原因:
異步任務:會開辟新線程,可以繞過任務不執行,回頭再執行
全局隊列:跟并發隊列一樣,任務同時執行,不過全局隊列有優先級設置
3.應用場景
(1)比如加載一些圖片,處理大型數據等耗時操作,可以放在子線程中執行,在返回主線程刷新UI。
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//耗時操作...
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程,刷新UI
});
});
(2)gcd實現定時器
NSInteger count = 0;
- (void)time {
//注意事項:dispatch_source_t最好用全局,局部不加dispatch_cancel,定時器不會被執行,因為還沒到回調timer就被釋放了。
//創建一個定時器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
//設置定時器
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//設置回調
dispatch_source_set_event_handler(timer, ^{
NSLog(@"第%ld次執行",count);
count ++;
if (count > 6) {
//取消定時器
dispatch_cancel(timer);
}
});
//啟動定時器
dispatch_resume(timer);
}
(3)gcd延遲執行
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// 3秒后異步執行這里的代碼...
NSLog(@"after");
});
}
(4)gcd只執行一次
- (void)once {
for (int i = 0; i < 3; i ++) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"xxx");
});
}
}
(5)dispatch_apply,可以實現遍歷數組效果
- (void)apply {
NSArray *arr = @[@"1",@"2",@"3",@"4",@"5",@"6"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([arr count], queue, ^(size_t index) {
NSLog(@"%zu : %@",index,arr[index]);
});
}
打印結果:
(6)GCD的隊列組dispatch_group,等多個異步操作結束后,再回到主線程
- (void)group {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"第一個耗時任務%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"第二個耗時任務%@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"回到主線程%@",[NSThread currentThread]);
});
}
打印結果:
(7)柵欄方法 dispatch_barrier_async,可以分割異步線程順序
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務1%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務2%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"任務分割%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務3%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任務4%@",[NSThread currentThread]);
});
}
打印結果:
(8)信號量 dispatch_semaphore_t
應用場景:假如有多個網絡請求,我們要求按順序執行,也就是網絡1請求結束之后再請求網絡2,以此類推。。。
由于網絡請求是異步的,想要其同步執行該怎么實現呢?有的人就會想說:我在網絡請求1結束回調里請求網絡2,再在網絡2請求結束里請求網絡3,這當然可以實現,但是這種方法對于少數請求還好,假如有10個,100個你還這樣寫,不說代碼量,就是看上去都會覺得很low。這時候信號量就派上用場了,看代碼:
NSLog(@"start");
dispatch_semaphore_t sema= dispatch_semaphore_create(0);
dispatch_async(dispatch_queue_create("d", DISPATCH_QUEUE_CONCURRENT), ^{
for (int i = 0; i < 10; i ++) {
[[BPNetworkTool sharedTools] GET:@"http://s.budejie.com/topic/list/zuixin/41/bs0315-iphone-4.5.6/0-20.json" parameters:nil success:^(id obj) {
NSLog(@"%d",i);
dispatch_semaphore_signal(sema);
} failure:^(NSError *error) {
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
});
打印結果:
我們從代碼中看到,信號量用到了三個方法:
1.dispatch_semaphore_t sema= dispatch_semaphore_create(0);
2. dispatch_semaphore_signal(sema);
3.dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
方法一:代表先創建一個信號量,下面的參數代表信號量的個數。
方法二:表示發送一個信號,信號量+1。
方法三:表示等待信號,第二個參數表示等待時間,當信號數量少于0時會一直等待,反之可以繼續執行下面方法,并且信號量-1。
(9)suspend/resume(隊列掛起和恢復)
suspend: 通過 dispatch_suspend() 函數實現隊列的”掛起”,使隊列暫停工作。但是這里的“掛起”,并不能立即停止隊列上正在運行的block;
resume: dispatch_resume() 函數恢復隊列,是隊列繼續工作。
注意:
1. dispatch_suspend 與 dispatch_resume 要成對出現。
2.dispatch_suspend在前,dispatch_resume在后。
代碼實現:
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
for (int i = 0; i < 5; i ++) {
dispatch_async(queue, ^{
NSLog(@"任務%d開始",i);
sleep(3);
NSLog(@"任務%d結束",i);
});
}
NSLog(@"任務創建完成");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_suspend(queue);
NSLog(@"隊列掛起");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_resume(queue);
NSLog(@"隊列恢復");
});
});
打印結果:
2017-12-06 17:14:59.740 GCD[6995:1631360] 任務創建完成
2017-12-06 17:14:59.740 GCD[6995:1631625] 任務0開始
2017-12-06 17:15:02.746 GCD[6995:1631625] 任務0結束
2017-12-06 17:15:02.746 GCD[6995:1631625] 任務1開始
2017-12-06 17:15:05.747 GCD[6995:1631625] 任務1結束
2017-12-06 17:15:05.747 GCD[6995:1631625] 任務2開始
2017-12-06 17:15:06.741 GCD[6995:1631360] 隊列掛起
2017-12-06 17:15:08.750 GCD[6995:1631625] 任務2結束
2017-12-06 17:15:12.237 GCD[6995:1631360] 隊列恢復
2017-12-06 17:15:12.237 GCD[6995:1631625] 任務3開始
2017-12-06 17:15:15.242 GCD[6995:1631625] 任務3結束
2017-12-06 17:15:15.243 GCD[6995:1631625] 任務4開始
2017-12-06 17:15:18.243 GCD[6995:1631625] 任務4結束
通過打印結果我們可以驗證當調用 dispatch_suspend(queue) “掛起”隊列 queue 后已經開始執行的任務不會掛起,而未開始的任務可以掛起。
(10) dispatch_set_target_queue (更改隊列的類型)
不管是串行隊列還是并行隊列都可以將其改為串行隊列。
使用的函數:dispatch_set_target_queue(dispatch_object_t object,
dispatch_queue_t _Nullable queue);
第一個參數:是指要更改優先級的隊列。
第二個參數:目標參照物,將要更改的隊列優先級與其相同。
代碼實現:
dispatch_queue_t queue1 = dispatch_queue_create("test1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("test2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("test3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, queue3);
dispatch_set_target_queue(queue2, queue3);
dispatch_async(queue1, ^{
NSLog(@"任務1");
});
dispatch_async(queue2, ^{
NSLog(@"任務2");
});
dispatch_async(queue3, ^{
NSLog(@"任務3");
});
打印結果:
2017-12-06 17:22:48.605 GCD[7162:1758848] 任務1
2017-12-06 17:22:48.606 GCD[7162:1758848] 任務2
2017-12-06 17:22:48.606 GCD[7162:1758848] 任務3
從打印結果可以看出用dispatch_set_target_queue()函數可以將并行隊列和串行隊列,改成串行隊列。
未完待續。。。