一.進(jìn)程&線程
- 進(jìn)程:是程序執(zhí)行過程中
分配和管理資源的一個(gè)基本單位。 - 線程:是程序執(zhí)行過程中
任務(wù)調(diào)度和執(zhí)行的一個(gè)基本單位。 - 一個(gè)進(jìn)程里面有多個(gè)線程,
線程是進(jìn)程的一部分。
二. 任務(wù)
任務(wù):就是要執(zhí)行什么操作。
1. 同步執(zhí)行(sync):
- 在當(dāng)前的線程上面執(zhí)行任務(wù)。
- 不具備開啟新線程的能力。
2. 異步執(zhí)行(async):
- 可以開啟新的線程執(zhí)行任務(wù)。
- 具備開啟新線程的能力。
三. 隊(duì)列
隊(duì)列:用于存放任務(wù),遵循FIFO(先進(jìn)先出)的原則。
1. 串行隊(duì)列(serial):
- 隊(duì)列中每次只執(zhí)行一個(gè)任務(wù),當(dāng)?shù)谝粋€(gè)執(zhí)行完才能執(zhí)行第二個(gè)。
- 只能開啟一個(gè)線程。
2. 并發(fā)隊(duì)列(concurrent):
- 隊(duì)列中可以同時(shí)執(zhí)行多個(gè)任務(wù)。
- 可以開啟多個(gè)線程。
3. 兩種特殊隊(duì)列:
- 全局隊(duì)列(
dispatch_get_global_queue):直接作為普通并發(fā)隊(duì)列使用。 - 主隊(duì)列(
dispatch_get_main_queue):任務(wù)在主線程中執(zhí)行的串行隊(duì)列。
四. 使用步驟
注:把任務(wù)放到...(主隊(duì)列 & 串行隊(duì)列 & 全局隊(duì)列)隊(duì)列中...(同步 & 異步)執(zhí)行。
1. 創(chuàng)建隊(duì)列
- 串行隊(duì)列 & 并發(fā)隊(duì)列
// 串行隊(duì)列
dispatch_queue_t serialQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_CONCURRENT);
注:第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符,DEBUG的時(shí)候用的,可以為空,推薦使用類似于APP的BundleID這種逆序域名;第二個(gè)參數(shù)識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列,串行隊(duì)列用DISPATCH_QUEUE_SERIAL,并發(fā)隊(duì)列用DISPATCH_QUEUE_CONCURRENT。
- 主隊(duì)列 & 全局隊(duì)列
// 主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局隊(duì)列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
注:主隊(duì)列不用傳參數(shù)。全局隊(duì)列第一個(gè)參數(shù)一般用DISPATCH_QUEUE_PRIORITY_DEFAULT,表示優(yōu)先級(jí)的。第二個(gè)參數(shù)暫時(shí)沒用,用0即可。
2. 創(chuàng)建任務(wù)
// 同步任務(wù)創(chuàng)建
dispatch_sync(queue, ^{
});
// 異步任務(wù)創(chuàng)建
dispatch_async(queue, ^{
});
注:參數(shù)queue就是隊(duì)列的類型。
3. 小結(jié)
-
并發(fā)隊(duì)列和全局隊(duì)列使用場(chǎng)景是一致的,通常都是使用全局隊(duì)列。 - 總共有
3種隊(duì)列:全局隊(duì)列,主隊(duì)列,串行隊(duì)列;有2種任務(wù):同步任務(wù),異步任務(wù)。 - 共有
6種使用方式,能否開啟新的線程有同步或者異步決定,但是否開啟新的線程要看當(dāng)前狀況是否需要開啟新的線程來決定。 -
sync會(huì)照成阻塞現(xiàn)象,sync任務(wù)下的隊(duì)列里面的任務(wù)要必須完成一個(gè)才能繼續(xù)下一個(gè)。 -
sync阻塞的是隊(duì)列,不是線程。
五. 使用
1. 同步主隊(duì)列 syncMain
解讀:把任務(wù)放到主隊(duì)列中同步執(zhí)行。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
如果在主線程中執(zhí)行
syncMain,會(huì)出現(xiàn)死鎖現(xiàn)象。
解釋:在主線程上面執(zhí)行同步主隊(duì)列syncMain任務(wù),相當(dāng)于把syncMain任務(wù)加到了主隊(duì)列Main中。當(dāng)要執(zhí)行任務(wù)1的時(shí)候,也要先把任務(wù)1加到Main中。因?yàn)槭峭饺蝿?wù)sync,所以要執(zhí)行任務(wù)1之前,必須先等待syncMain任務(wù)完成。但是要完成syncMain任務(wù)前,又必須執(zhí)行任務(wù)1,這時(shí)候syncMain任務(wù)和任務(wù)1之間就會(huì)互相等待,出現(xiàn)死鎖現(xiàn)象,程序崩潰。如果是在子線程中使用
syncMain,不開啟新的線程。先完成任務(wù)1,再完成任務(wù)2。
解釋:因?yàn)槭峭饺蝿?wù)sync,不具備開啟新的線程的能力。在子線程中執(zhí)行syncMain,相當(dāng)于吧syncMain任務(wù)加到了子線程的隊(duì)列(這里用隊(duì)列A表示)中。當(dāng)要執(zhí)行任務(wù)1和任務(wù)2的時(shí)候,把任務(wù)1和任務(wù)2加到主隊(duì)列Main中,這個(gè)時(shí)候,Main中是沒有其他任務(wù)的,所以不會(huì)出現(xiàn)死鎖現(xiàn)象。Main是特殊的串行隊(duì)列,所以先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2。
2. 異步主隊(duì)列 asyncMain
解讀:把任務(wù)放到主隊(duì)列中異步執(zhí)行。
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
不開啟新的線程。先完成任務(wù)1,再完成任務(wù)2。
解釋:雖然是異步任務(wù)async,具備開啟新的線程的能力。但是由于是在主隊(duì)列Main中執(zhí)行任務(wù),Main中的任務(wù)必須在主線程完成,所以不需要開啟新的線程。在主線程中執(zhí)行asyncMain任務(wù),相當(dāng)于把asyncMain任務(wù)加到主隊(duì)列Main中。當(dāng)要執(zhí)行任務(wù)1的時(shí)候,也要任務(wù)1加到Main中。因?yàn)槭钱惒饺蝿?wù)async,所以asyncMain任務(wù)可以先等待,先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2,所以不會(huì)出現(xiàn)死鎖現(xiàn)象;在子線程執(zhí)行asyncMain任務(wù),跟子線程執(zhí)行syncMain任務(wù)同等邏輯。
使用場(chǎng)景:做網(wǎng)絡(luò)請(qǐng)求從后臺(tái)接口獲取到數(shù)據(jù)之后,需要根據(jù)數(shù)據(jù)更新界面UI,一般都是用asyncMain,在asyncMain的block里面執(zhí)行刷新界面的操作。
3.同步串行隊(duì)列 syncSerial
解讀:把任務(wù)放到串行隊(duì)列中同步執(zhí)行。
dispatch_sync(dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
不會(huì)開啟新的線程。先完成任務(wù)1,再完成任務(wù)2。
解釋:因?yàn)槭峭饺蝿?wù)sync,sync不具備開啟新的線程。執(zhí)行syncSerial任務(wù)時(shí),syncSerial任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列B表示)中,執(zhí)行任務(wù)1的時(shí)候,將任務(wù)1放到當(dāng)前線程的串行隊(duì)列Serial中,任務(wù)2也一樣。因?yàn)槭?code>Serial,所以要先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2。
問題:同步串行隊(duì)列syncSerial在主線程上執(zhí)行,為什么不會(huì)出現(xiàn)死鎖現(xiàn)象?
回答:在主線程上執(zhí)行syncSerial任務(wù),syncSerial任務(wù)存放在主隊(duì)列Main當(dāng)中,而任務(wù)1和任務(wù)2都放在主線程的串行隊(duì)列Serial中(不是在主隊(duì)列Main中哦~)。此時(shí),主線程上面有兩個(gè)隊(duì)列,一個(gè)是存放syncSerial任務(wù)的Main,另一個(gè)是存放任務(wù)1和任務(wù)2的Serial。當(dāng)執(zhí)行syncSerial任務(wù)中的任務(wù)1時(shí),會(huì)從主隊(duì)列Main去到串行隊(duì)列Serial,然后在Serial繼續(xù)執(zhí)行任務(wù)2,執(zhí)行完任務(wù)2,回到主隊(duì)列Main中完成syncSerial任務(wù)。
4.異步串行隊(duì)列 asyncSerial
解讀:把任務(wù)放到串行隊(duì)列中異步執(zhí)行。
dispatch_async(dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
會(huì)開啟一條新的線程。先完成任務(wù)1,再完成任務(wù)2。
解釋:因?yàn)槭钱惒饺蝿?wù)async,具備開啟新的線程的能力。因?yàn)槭窃诖嘘?duì)列Serial中,只能同時(shí)執(zhí)行一個(gè)任務(wù),所以只需要開啟一條新的線程。asyncSerial任務(wù)存放在當(dāng)前線程的隊(duì)列中(這里用隊(duì)列C表示),而任務(wù)1和任務(wù)2存放在新開啟的線程的Serial。當(dāng)執(zhí)行asyncSerial任務(wù),要開始執(zhí)行任務(wù)1時(shí),先去到新開啟的線程的Serial中,執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2,然后回到隊(duì)列C中完成asyncSerial任務(wù)。
5. 同步全局隊(duì)列 syncGlobal
解讀:把任務(wù)放到全局隊(duì)列中同步執(zhí)行。
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
不會(huì)開啟新的線程。先完成任務(wù)1,再完成任務(wù)2。
解釋:因?yàn)槭峭饺蝿?wù)sync,不具備開啟新的線程能力。雖然是在全局隊(duì)列Global中,可以多個(gè)任務(wù)同時(shí)進(jìn)行,但是只有一條線程,所以還是要先完成任務(wù)1再執(zhí)行任務(wù)2。syncGlobal任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列D表示)中,執(zhí)行任務(wù)1的時(shí)候,將任務(wù)1放到當(dāng)前線程的全局隊(duì)列Global中,任務(wù)2也一樣。雖然是Global,但是只有一條線程,所以要先執(zhí)行完任務(wù)1,再執(zhí)行任務(wù)2。
使用場(chǎng)景:上傳多張圖片到后臺(tái),后臺(tái)要求一張一張的上傳。
6. 異步全局隊(duì)列 asyncGlobal
解讀:把任務(wù)放到全局隊(duì)列中異步執(zhí)行。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)2");
});
會(huì)開啟多條新的線程。任務(wù)1和任務(wù)2同時(shí)執(zhí)行。
解釋:因?yàn)槭钱惒饺蝿?wù)async,async會(huì)具備開啟新的線程能力。因?yàn)槭侨株?duì)列Global,所以Global里面的任務(wù)可以同時(shí)執(zhí)行。asyncGlobal任務(wù)存放在當(dāng)前線程的隊(duì)列(這里用隊(duì)列E表示)中,而任務(wù)1和任務(wù)2存放在各自開啟的線程隊(duì)列中。當(dāng)執(zhí)行asyncGlobal任務(wù),因?yàn)槭?code>Global,所以任務(wù)1和任務(wù)2可以同時(shí)執(zhí)行。
使用場(chǎng)景:上傳多張圖片到后臺(tái),可以多張同時(shí)上傳。
六.GCD線程之間的通信
異步開啟子線程執(zhí)行耗時(shí)任務(wù),耗時(shí)任務(wù)完成,利用主隊(duì)列回到主線程更新UI。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 10; i ++) {
NSLog(@"---%d",i);
// 模擬耗時(shí)操作
[NSThread sleepForTimeInterval:1];
}
});
dispatch_async(dispatch_get_main_queue(), ^{
// 耗時(shí)操作完成
NSLog(@"任務(wù)完成,回到主線程更新UI。");
});
});
七.阻塞方法dispatch_barrier
作用:在有多個(gè)任務(wù)并且使用柵欄方法dispatch_barrier的隊(duì)列(注意:不能使用全局隊(duì)列),必須先等待dispatch_barrier前面的任務(wù)執(zhí)行完畢,才能執(zhí)行dispatch_barrier里面的任務(wù)。等待dispatch_barrier里面的任務(wù)執(zhí)行完畢,才能繼續(xù)執(zhí)行dispatch_barrier之后的任務(wù)。
例子:有三種圖片,分別壓縮之后,一起上傳后后臺(tái)。
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.wxh.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"壓縮圖片1");
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"壓縮圖片2");
});
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"壓縮圖片3");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"將 壓縮圖片1 壓縮圖片2 壓縮圖片3 上傳到后臺(tái)");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"其他操作");
});
結(jié)果:這樣子既能讓3張圖片同時(shí)壓縮,又能確保3張圖片都?jí)嚎s完之后,才將3張圖片上傳到后臺(tái)。
問題1:為什么不能使用 全局隊(duì)列 ?
解釋:蘋果官方給的說明是如果使用全局隊(duì)列,那么dispatch_barrier_async方法將退化成dispatch_async方法。個(gè)人覺得,不知道對(duì)不對(duì),全局隊(duì)列沒有名字,自定義的并發(fā)隊(duì)列是有名字的,系統(tǒng)需要重新控制隊(duì)列里面任務(wù)的執(zhí)行操作,必須具體到哪個(gè)隊(duì)列中去重新控制。
問題2:dispatch_barrier_asyn和dispatch_barrier_syn的區(qū)別?
解釋:上例中,將dispatch_barrier_asyn替換成dispatch_barrier_syn效果是一樣的。它們的區(qū)別在于,
-
dispatch_barrier_asyn將自己的任務(wù)加入到隊(duì)列中之后,不用等自己的任務(wù)執(zhí)行完畢,它就將它后面的任務(wù)也加入到隊(duì)列中。然后等待自己的任務(wù)執(zhí)行完畢,才執(zhí)行后面的任務(wù)。 -
dispatch_barrier_syn將自己的任務(wù)加入到隊(duì)列中之后,需要等待自己的任務(wù)執(zhí)行完畢,才能加入后臺(tái)的任務(wù),并執(zhí)行后面的任務(wù)。
八.延時(shí)方法 dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
方法中需要傳入一個(gè)延時(shí)的時(shí)間(秒),延時(shí)操作里面的任務(wù)放到主隊(duì)列執(zhí)行。
九.只執(zhí)行一次(單例) dispatch_once
+ (instancetype)shareInstance{
static Singleton *single;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
single = [[Singleton alloc] init];
});
return single;
}
在程序運(yùn)行過程中,dispatch_once方法中的代碼只會(huì)被執(zhí)行1次,即不影響性能,又能保證線程安全。
-
原理:
dispatch_once方法是根據(jù)dispatch_once_t修飾的變量onceToken的值來決定接下來的操作的。
(1)當(dāng)onceToken = 0時(shí),說明程序第一次執(zhí)行dispatch_once方法,直接執(zhí)行dispatch_once的block中的代碼。
(2)當(dāng)onceToken = -1時(shí),說明程序已經(jīng)執(zhí)行完過dispatch_once方法,那么跳過dispatch_once的block的代碼,執(zhí)行block之后的代碼。
(3)當(dāng)onceToken != 0且onceToken != -1時(shí),說明現(xiàn)在有線程(這里用線程A表示)在執(zhí)行dispatch_once方法,但是還沒執(zhí)行完畢。這個(gè)時(shí)候,當(dāng)前這條線程處于阻塞狀態(tài),等待線程A執(zhí)行完畢。當(dāng)線程A執(zhí)行完dispatch_once方法時(shí),onceToken的值會(huì)變成-1,這時(shí)候當(dāng)前這條線程繼續(xù)執(zhí)行。
(4)單例可以看成是一種特殊的實(shí)例,是一個(gè)全局的對(duì)象,有且只有一個(gè)的對(duì)象。
十.快速迭代 dispatch_apply
NSLog(@"---");
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
// 模擬耗時(shí)操作
[NSThread sleepForTimeInterval:(11 - index)];
NSLog(@"%zu",index);
});
NSLog(@"+++");
---
3
2
1
0
6
5
4
7
9
8
+++
dispatch_apply是一個(gè)快速迭代的方法,類似于for循環(huán)。
- 如果方法中傳入的是一個(gè)
串行隊(duì)列,那么dispatch_apply里面的耗時(shí)操作就需要按順序同步執(zhí)行(相當(dāng)于異步串行隊(duì)列,必須執(zhí)行完成一個(gè)任務(wù)之后,才能執(zhí)行下一個(gè)任務(wù)。不過一般不會(huì)這么做,這樣操作就失去了快速迭代的意義)。 - 如果方法中傳入的是一個(gè)
全局隊(duì)列,那么里面多個(gè)耗時(shí)操作就可以同時(shí)進(jìn)行(相當(dāng)于異步并發(fā)隊(duì)列,可以多個(gè)任務(wù)同時(shí)執(zhí)行)。 - 無論傳入的是
串行隊(duì)列還是全局隊(duì)列,dispatch_apply方法都會(huì)阻塞當(dāng)前線程等待所有任務(wù)執(zhí)行完畢,才能執(zhí)行dispatch_apply方法后面的代碼(相當(dāng)于同步任務(wù))。
十一. 隊(duì)列組 dispatch_group
需求:在填寫個(gè)人資料頁面,我們需要把個(gè)人的信息(名字,手機(jī)號(hào)等)上傳到后臺(tái),也需要把照片(身份證正反面拍照等)也上傳到后臺(tái),需要做兩個(gè)網(wǎng)絡(luò)請(qǐng)求。當(dāng)兩個(gè)網(wǎng)絡(luò)請(qǐng)求都成功回調(diào)之后,返回上一個(gè)頁面。
1. 第一種方法:使用dispatch_group_notify監(jiān)聽。
NSLog(@"---");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:3];
NSLog(@"上傳個(gè)人信息");
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"上傳圖片資料");
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"返回上個(gè)界面");
});
NSLog(@"===");
---
===
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
-
dispatch_group_create()創(chuàng)建一個(gè)隊(duì)列組。 -
dispatch_group_async將任務(wù)放到隊(duì)列里面,然后再講隊(duì)列放到隊(duì)列組里面。 -
dispatch_group_notify監(jiān)聽隊(duì)列組中其他隊(duì)列的任務(wù)完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成之后,將自身block里面的任務(wù)也方法隊(duì)列組中,執(zhí)行任務(wù)。 -
dispatch_group_notify不會(huì)阻塞當(dāng)前線程。
2. 第二種方法:使用dispatch_group_wait阻塞當(dāng)前線程。
NSLog(@"---");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:3];
NSLog(@"上傳個(gè)人信息");
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"上傳圖片資料");
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"返回上個(gè)界面");
NSLog(@"===");
---
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
===
- 當(dāng)所有任務(wù)都完成之后,才執(zhí)行
dispatch_group_wait后面的任務(wù)。 -
dispatch_group_wait會(huì)阻塞當(dāng)前的線程。
3. 第三種方法:使用dispatch_group_enter和dispatch_group_leave組合代替dispatch_group_async。
NSLog(@"---");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:3];
NSLog(@"上傳個(gè)人信息");
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i < 3; ++i) {
[NSThread sleepForTimeInterval:1];
NSLog(@"上傳圖片資料");
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"返回上個(gè)界面");
});
NSLog(@"===");
---
===
上傳圖片資料
上傳圖片資料
上傳個(gè)人信息
上傳圖片資料
上傳個(gè)人信息
返回上個(gè)界面
-
dispatch_group_enter表示把一個(gè)任務(wù)放到隊(duì)列組group,并開始執(zhí)行。 -
dispatch_group_leave表示任務(wù)執(zhí)行完畢。 -
dispatch_group_enter+dispatch_group_leave=dispatch_group_async - 可以用
dispatch_group_notify,同樣也可以用dispatch_group_wait(結(jié)果跟第2種方法一樣)。區(qū)別是后者會(huì)照成當(dāng)前線程阻塞,前者不會(huì)。 - 當(dāng)
group中的所有任務(wù)都執(zhí)行完畢時(shí),才會(huì)執(zhí)行dispatch_group_wait后面的任務(wù),或者執(zhí)行追加到dispatch_group_notify中的任務(wù)。
十二. 信號(hào)量 dispatch_semaphore
三個(gè)重要方法
-
dispatch_semaphore_create:創(chuàng)建并初始化一個(gè)信號(hào)總量,一般為0或者1。 -
dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào),即讓信號(hào)總量+1。 -
dispatch_semaphore_wait:如果當(dāng)前信號(hào)總量為0,那么阻塞當(dāng)前線程,否則。信號(hào)總量-1,正常執(zhí)行。
1. 異步線程變成同步。
需求:有時(shí)候需要實(shí)時(shí)拿到異步里面耗時(shí)操作的結(jié)果,才能正確的執(zhí)行之后的代碼。
__block NSInteger i = 1;
// 創(chuàng)建一個(gè)信號(hào)總量為`0`的信號(hào)量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
i ++;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%ld",(long)i);
打印結(jié)果為:2
解釋:當(dāng)?shù)谝淮螆?zhí)行dispatch_semaphore_wait時(shí),信號(hào)總量為0,當(dāng)前線程阻塞。當(dāng)執(zhí)行完異步block里面的耗時(shí)操作之后,執(zhí)行了dispatch_semaphore_signal,信號(hào)總量+1。從block里面出來第二次執(zhí)行dispatch_semaphore_wait時(shí),信號(hào)總量為1,正常執(zhí)行。這樣就能等到異步執(zhí)行完之后,再執(zhí)行接下來的代碼(類似于同步執(zhí)行)。
2. 線程安全(線程鎖)
需求:有時(shí)候,我們會(huì)在多個(gè)地方同時(shí)對(duì)同一個(gè)接口進(jìn)行調(diào)用,那如果每次調(diào)用過程會(huì)對(duì)下一次調(diào)用的結(jié)果有影響(有修改或者更變等操作),那么我們就必須保證該接口同一時(shí)間只能被一個(gè)地方調(diào)用,這就是線程安全。
- (void)viewDidLoad {
[super viewDidLoad];
self.semaphore = dispatch_semaphore_create(1);
}
- (void)tiaoyongjiekou{
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模擬耗時(shí)操作
[NSThread sleepForTimeInterval:1.0];
dispatch_semaphore_signal(weakSelf.semaphore);
});
}
解釋:在外部創(chuàng)建一個(gè)信號(hào)總量為1的信號(hào)量,當(dāng)?shù)谝淮握{(diào)用tiaoyongjiekou方法,執(zhí)行到dispatch_semaphore_wait時(shí),因?yàn)楫?dāng)前信號(hào)總量為1,那么正常執(zhí)行并且信號(hào)總量-1(此時(shí)信號(hào)總量為0)。如果第一次調(diào)用還沒執(zhí)行完成,第二次就開始調(diào)用,當(dāng)執(zhí)行到dispatch_semaphore_wait時(shí),信號(hào)總量為0,線程阻塞,只能原地等待。等第一次調(diào)用結(jié)束,執(zhí)行完耗時(shí)操作之后,執(zhí)行了dispatch_semaphore_signal,信號(hào)總量+1(此時(shí)信號(hào)總量為1),那么第二次調(diào)用才能繼續(xù)執(zhí)行。這樣就能確保同一時(shí)間只被調(diào)用一次,確保線安全。