關(guān)鍵詞:異步執(zhí)行任務(wù)的技術(shù)巩搏、將應(yīng)用程序的線程管理用的代碼在系統(tǒng)級(jí)中實(shí)現(xiàn)、高效率暑诸。
舊的 API 實(shí)現(xiàn)
- (void)demoPerformSelector{
[self performSelectorInBackground:@selector(doWork) withObject:nil];
}
- (void)doWork{
NSLog(@"doWork........");
[self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
}
- (void)doneWork{
NSLog(@"doneWork!");
}
使用 GCD 實(shí)現(xiàn)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"doWork........");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"doneWork!");
});
});
1.Dispatch Queue 調(diào)度隊(duì)列
Dispatch Queue 先進(jìn)先出的屬性(FIFO) 執(zhí)行處理。有兩種 Dispatch Queue,一種是等待現(xiàn)在執(zhí)行中處理的 Serial Dispatch Queue(串行調(diào)度隊(duì)列)宜狐,另一種是不等待現(xiàn)在執(zhí)行中處理的 Concurrent Dispatch Queue(并行調(diào)度隊(duì)列)。
通過以下源碼蛇捌,比較這兩種 Dispatch Queue:
dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);
dispatch_async(queue,block5);
當(dāng) queue 為 Serial Dispatch Queue 時(shí)抚恒,因?yàn)橐鹊浆F(xiàn)在執(zhí)行中處理結(jié)束,所以首先執(zhí)行 block0络拌,block0執(zhí)行結(jié)束后俭驮,執(zhí)行 block1,如此重復(fù)春贸,同時(shí)執(zhí)行的處理數(shù)只能有1個(gè)混萝。即執(zhí)行該源代碼后,一定按照以下順序進(jìn)行處理萍恕。(block0逸嘀,block1,block2允粤,block3崭倘,block4,block5)类垫。
當(dāng) queue 為 Concurrent Dispatch Queue 時(shí)司光,因?yàn)椴挥玫却F(xiàn)在執(zhí)行中的處理結(jié)束,所以首先執(zhí)行 block0悉患,不管 block0 的執(zhí)行是否結(jié)束残家,都開始執(zhí)行后面的 block1,不管 block1執(zhí)行是否結(jié)束了售躁,都開始執(zhí)行后面的 block2坞淮,如此重復(fù)循環(huán)。
這樣雖然不用等待處理結(jié)束陪捷,可以并行執(zhí)行多個(gè)處理回窘,但并行執(zhí)行處理的數(shù)量取決于當(dāng)前系統(tǒng)的狀態(tài)。即 iOS 或 OS X 基于 Dispatch Queue 中的處理數(shù)揩局、CPU核數(shù)以及 CPU 負(fù)荷等當(dāng)前系統(tǒng)的狀態(tài)來決定 Concurrent Dispatch Queue 中并行執(zhí)行的處理數(shù)毫玖。所謂的“并行執(zhí)行”,就是使用多個(gè)線程同時(shí)執(zhí)行多個(gè)處理。
2.獲取 Dispatch Queue
使用 dispatch_queue_create
創(chuàng)建 Dispatch Queue
//創(chuàng)建 Serial Dispatch Queue
dispatch_queue_t mySerialDicpatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
//創(chuàng)建 Concurrent Dispatch Queue
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.exmaple.gcd.MuConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
需要注意的是 dispatch_queue_create
函數(shù)可以創(chuàng)建任意多個(gè) Dispatch Queue付枫,當(dāng)生成多個(gè) Serial Dispatch Queue 時(shí)烹玉,雖然一個(gè) Seria Dispatch Queue 中同時(shí)只能執(zhí)行一個(gè)追加處理,但如果將處理分別追加到多個(gè) Serial Dispatch Queue 中阐滩,各個(gè) Serial Dispatch Queue 會(huì)分別執(zhí)行二打,即同時(shí)執(zhí)行多個(gè)處理。一旦生成 Serial Dispatch Queue 并追加處理掂榔,系統(tǒng)對(duì)于一個(gè) Serial Dispatch Queue 就只生成并使用一個(gè)線程继效。如果生成 N 個(gè) Serial Dispatch Queue,那么就生成 N 個(gè)線程装获。
Main Dispatch Queue 和 Global Dispatch Queue
Main Dispatch Queue瑞信,主線程隊(duì)列,是一個(gè) Serial Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
Global Dispatch Queue穴豫,全局隊(duì)列凡简,是 Concurrent Dispatch Queue,有四種優(yōu)先級(jí)High Priority
精肃、Default Priority
秤涩、Low Priority
、Background Priority
司抱。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. dispatch_set_target_queue()
設(shè)置參考隊(duì)列
dispatch_set_target_queue(dispatch_object_t object,
dispatch_queue_t _Nullable queue);
該函數(shù)有兩種用法筐眷,第一種是設(shè)置 Dispatch Queue 的優(yōu)先級(jí)
第一個(gè)參數(shù)填需要更改優(yōu)先級(jí)的 Dispatch Queue,第二個(gè)參數(shù)填要與要指定的優(yōu)先級(jí)相同優(yōu)先級(jí)的 Global Dispatch Queue习柠。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.gcd.mySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalQueueLowPriority);
第二種用法可以用來線程同步
當(dāng)我們想讓不同隊(duì)列中的任務(wù)同步的執(zhí)行時(shí)匀谣,我們可以創(chuàng)建一個(gè)串行隊(duì)列,然后將這些隊(duì)列的 target 指向新創(chuàng)建的隊(duì)列
dispatch_queue_t serailQueue1 = dispatch_queue_create("com.gcd.serialQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serailQueue2 = dispatch_queue_create("com.gcd.serialQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue1 = dispatch_queue_create("com.gcd.concurrentQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t concurrentQueue2 = dispatch_queue_create("com.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
//創(chuàng)建目標(biāo)隊(duì)列(參考隊(duì)列)
dispatch_queue_t targetQueue = dispatch_queue_create("com.gcd.targetQueue", DISPATCH_QUEUE_SERIAL);
//設(shè)置參考
dispatch_set_target_queue(serailQueue1, targetQueue);
dispatch_set_target_queue(serailQueue2, targetQueue);
dispatch_set_target_queue(concurrentQueue1, targetQueue);
dispatch_set_target_queue(concurrentQueue2, targetQueue);
NSLog(@"******start******");
dispatch_async(serailQueue1, ^{
NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
sleep(3);
NSLog(@"task1 end");
});
dispatch_async(serailQueue2, ^{
NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
sleep(2);
NSLog(@"task2 end");
});
dispatch_async(concurrentQueue1, ^{
NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
sleep(1);
NSLog(@"task3 end");
});
dispatch_async(concurrentQueue2, ^{
NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
NSLog(@"task4 end");
});
NSLog(@"******end******");
輸出結(jié)果:
******start******
******end******
current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task1
task1 end
current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task2
task2 end
current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task3
task3 end
current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task4
task4 end
通過dispatch_set_target_queue()
函數(shù)以及參考隊(duì)列targetQueue
津畸,使得串行隊(duì)列serailQueue1
,serailQueue2
與并行隊(duì)列concurrentQueue1
,concurrentQueue2
同步振定。
4. dispatch_after()
延時(shí)執(zhí)行(準(zhǔn)確的說是追加任務(wù))
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"**********************");
});
注意:
dispatch_after()
函數(shù)并不是在指定時(shí)間后執(zhí)行任務(wù)必怜,而是在指定時(shí)間追加任務(wù)到 Dispatch Queue 中肉拓。
示例中與3秒后用 dispatch_async()
函數(shù)追加 block 中的任務(wù)到 Main Dispatch Queue 相同。
Main Dispatch Queue 在主線程的 RunLoop 中執(zhí)行梳庆,假設(shè)每隔 1/60秒執(zhí)行一次的 RunLoop暖途,block 最快在3s 后執(zhí)行,最慢在 3+1/60 秒后執(zhí)行膏执,并且在 Main Dispatch Queue 中有大量追加的任務(wù)或者主線程本身處理有延遲時(shí)驻售,時(shí)間會(huì)更長(zhǎng)。
dispatch_time_t
表示的是一個(gè)時(shí)刻更米,可以由 dispatch_time()
函數(shù)或者 dispatch_walltime()
函數(shù)獲得
dispatch_time()
函數(shù) 能夠獲取從第一個(gè)參數(shù)指定的時(shí)間開始欺栗,到第二個(gè)參數(shù)指定的納秒(毫微秒)后的時(shí)間 常用于計(jì)算相對(duì)時(shí)間
dispatch_walltime()
函數(shù)由 POSIX 中使用的 struct timespec 類型的時(shí)間得到 dispatch_time_t
類型的值,常用計(jì)算絕對(duì)時(shí)間
//ull 數(shù)值字面量(unsigned long long) DISPATCH_TIME_NOW 表示現(xiàn)在的時(shí)間
//獲取從現(xiàn)在開始1s 后的時(shí)間
dispatch_time_t time1 = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
//獲取從現(xiàn)在開始100毫秒后的時(shí)間
dispatch_time_t time2 = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
// 通過 NSDate 對(duì)象獲取 dispatch_time_t 類型值
dispatch_time_t getDispatchTimeByDate(NSDate *date){
NSTimeInterval interval = [date timeIntervalSince1970];
double second;
double subsecond = modf(interval, &second);
struct timespec time;
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
dispatch_time_t milestone = dispatch_walltime(&time, 0);
return milestone;
}
5. Dispatch Group
Dispatch Group可以用于在追加到 Dispatch Queue 中的多個(gè)任務(wù)全部結(jié)束后想執(zhí)行的結(jié)束任務(wù)的操作。
-
dispatch_group_create()
創(chuàng)建 Dispatch Group -
dispatch_group_async()
追加任務(wù)到指定的 Dispatch Queue 中迟几,且指定任務(wù)屬于指定的 Dispatch Group -
dispatch_group_notify()
所有任務(wù)執(zhí)行完畢后再追加執(zhí)行的任務(wù) -
dispatch_group_wati()
在指定的等待時(shí)間前(超時(shí)時(shí)間)消请,等待 group 中全部任務(wù)處理結(jié)束,會(huì)卡住當(dāng)前線程
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.gcd.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSLog(@"******start******");
dispatch_group_async(group, serialQueue, ^{
NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
sleep(3);
NSLog(@"task1 end");
});
dispatch_group_async(group, conCurrentQueue, ^{
NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
sleep(2);
NSLog(@"task2 end");
});
dispatch_group_async(group, serialQueue, ^{
NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
sleep(1);
NSLog(@"task3 end");
});
dispatch_group_async(group, conCurrentQueue, ^{
NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
NSLog(@"task4 end");
});
//第二個(gè)參數(shù)填超時(shí)時(shí)間 DISPATCH_TIME_FOREVER 表示永遠(yuǎn)等待
// long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 設(shè)置2秒的超時(shí)時(shí)間
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"Group 中所有任務(wù)執(zhí)行完畢");
}else{
NSLog(@"Group 中任有任務(wù)執(zhí)行中");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"current Thread:%@ All END",[NSThread currentThread]);
});
NSLog(@"******end******");
6. dispatch_barrier_async
dispatch_barrier_async
函數(shù)可以理解為一種分割任務(wù)的柵欄类腮,通過dispatch_barrier_async
追加的任務(wù)同時(shí)只執(zhí)行一個(gè)
dispatch_async(conCurrentQueue, read_block1);
dispatch_async(conCurrentQueue, read_block2);
dispatch_async(conCurrentQueue, read_block3);
dispatch_barrier_async(conCurrentQueue, write_block4);
dispatch_async(conCurrentQueue, read_block5);
dispatch_async(conCurrentQueue, read_block6);
dispatch_async(conCurrentQueue, read_block7);
示例中 block1臊泰,block2,block3 并行執(zhí)行蚜枢,都執(zhí)行完畢后會(huì)執(zhí)行 write_block4缸逃,然后block5,block6厂抽,block7再并行執(zhí)行需频。
使用 Concurrent Dispatch Queue 配合 dispatch_barrier_async
函數(shù)可以實(shí)現(xiàn)高效的數(shù)據(jù)庫訪問和文件訪問。
7. dispatch_sync
dispatch_sync
同步追加任務(wù)到隊(duì)列中筷凤,不能開辟線程贺辰,且只有在追加的任務(wù)完成后才返回
dispatch_sync
函數(shù)引起死鎖問題
產(chǎn)生死鎖的條件是在串行隊(duì)列所在的線程中,使用 dispatch_sync
函數(shù)追加任務(wù)到該串行隊(duì)列中嵌施。
示例一
//在主線程中調(diào)用以下代碼會(huì)產(chǎn)生死鎖
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"死鎖~~");
NSLog(@"current thread:%@",[NSThread currentThread]);
});
示例二
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.gcd.serialDispatchQueue", NULL);
dispatch_async(serialDispatchQueue, ^{
NSLog(@"current thread:%@",[NSThread currentThread]);
dispatch_sync(serialDispatchQueue, ^{
NSLog(@"死鎖");
});
});
示例一:由于主線程所在的隊(duì)列 Main Dispatch Queue 為一個(gè)串行隊(duì)列饲化,所以在主線程中使用dispatch_sync
函數(shù)同步追加任務(wù)到 Main Dispatch Queue 中會(huì)產(chǎn)生死鎖。
示例二:創(chuàng)建了串行隊(duì)列 serialDispatchQueue
吗伤,使用dispatch_async
異步追加任務(wù)到該隊(duì)列吃靠,此時(shí)該隊(duì)列中的任務(wù)都是在該隊(duì)列的線程上執(zhí)行,此時(shí)使用dispatch_sync
函數(shù)再同步追加任務(wù)到該隊(duì)列中足淆,由于是在串行隊(duì)列所在的線程中同步追加任務(wù)巢块,所以產(chǎn)生了死鎖。
dispatch_sync
函數(shù)引起死鎖的原因
- 調(diào)用
dispatch_sync
函數(shù)會(huì)立即阻塞調(diào)用時(shí)該函數(shù)所在的線程巧号,等待dispatch_sync
函數(shù)返回 - 由于追加任務(wù)的隊(duì)列為串行隊(duì)列所以族奢,采用 FIFO 的順序執(zhí)行任務(wù),很顯然我們追加的任務(wù)位于隊(duì)列后面丹鸿,現(xiàn)在不會(huì)立即執(zhí)行
- 如果任務(wù)不執(zhí)行完越走,
dispatch_sync
函數(shù)不會(huì)返回,所以線程會(huì)一直被阻塞
8. dispatch_apply
dispatch_apply
函數(shù)會(huì)按指定的次數(shù)將任務(wù) block 追加到指定的 Dispatch Queue 中靠欢,并等待全部處理執(zhí)行結(jié)束廊敌。
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.gcd.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, concurrentDispatchQueue, ^(size_t index) {
NSLog(@"%zu current thread:%@",index,[NSThread currentThread]);
});
NSLog(@"******end******");
執(zhí)行結(jié)果:0,2门怪,1骡澈,3,4掷空,5 end
因?yàn)槭窃?Concurrent Dispatch Queue 中執(zhí)行任務(wù)的肋殴,所以幾個(gè)任務(wù)是并行執(zhí)行囤锉。
注意: dispatch_apply
函數(shù)會(huì)阻塞當(dāng)前線程,等待任務(wù)全部執(zhí)行完畢再返回护锤,所以在主線程中調(diào)用追加任務(wù)到 Main Dispatch Queue 會(huì)造成死鎖嚼锄。
9. dispatch_suspend
和dispatch_resume
函數(shù)
dispatch_suspend
函數(shù)用于掛起指定的 Dispatch Queue
dispatch_resume
函數(shù)用于恢復(fù)指定的 Dispatch Queue
這些函數(shù)對(duì)已經(jīng)開始執(zhí)行的任務(wù)沒有影響,掛起后蔽豺,追加到 Dispatch Queue 中区丑,但尚未執(zhí)行的任務(wù)在此之后會(huì)暫停執(zhí)行(任務(wù)仍然可以繼續(xù)追加,但新追加的也會(huì)暫停執(zhí)行)修陡,而恢復(fù)則使得這些任務(wù)繼續(xù)執(zhí)行沧侥。
10. Dispatch Semaphore 信號(hào)量
Dispatch Semaphore 信號(hào)量在 GCD 常被用于進(jìn)行同步和控制并發(fā)。
信號(hào)量是一個(gè)整形值并且具有一個(gè)初始計(jì)數(shù)值魄鸦,并且支持兩個(gè)操作:信號(hào)通知和等待宴杀。當(dāng)一個(gè)信號(hào)量被信號(hào)通知,其計(jì)數(shù)會(huì)被加1拾因。當(dāng)在一個(gè)線程上設(shè)置一個(gè)信號(hào)量等待時(shí)旺罢,線程會(huì)被阻塞至超時(shí)時(shí)間(如果有必要的話可以設(shè)置為一直阻塞),只有當(dāng)計(jì)數(shù)器大于零绢记,計(jì)數(shù)才會(huì)被減1并且該線程恢復(fù)扁达。
信號(hào)量可以被用來作為線程鎖,也可以用來控制并發(fā)線程數(shù)蠢熄。
//如果設(shè)置為10的話跪解,并發(fā)線程最多為10個(gè)
// dispatch_semaphore_t sema = dispatch_semaphore_create(10);
//如果設(shè)置為1的話,并發(fā)線程為1個(gè)签孔,可以保證數(shù)據(jù)安全
dispatch_semaphore_t sema = dispatch_semaphore_create(1);
dispatch_queue_t gloabQueue = dispatch_get_global_queue(0, 0);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
for (int i = 0; i < 100; i++) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(gloabQueue, ^{
NSLog(@"thread%@ add %d",[NSThread currentThread] ,i);
[array addObject:@(i)];
dispatch_semaphore_signal(sema);
});
}
11. dispatch_once
dispatch_once
函數(shù)是保證在應(yīng)用程序執(zhí)行中執(zhí)行一次指定處理的 API叉讥。
使用dispatch_once
函數(shù)生成單利,即使在多線程情況下執(zhí)行饥追,也可保證百分百安全图仓。
static dispatch_once_t pred;
dispatch_once(&pred, ^{
NSLog(@"只會(huì)執(zhí)行一次");
});