關(guān)鍵詞:異步執(zhí)行任務(wù)的技術(shù)、將應(yīng)用程序的線程管理用的代碼在系統(tǒng)級中實(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)對于一個(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)先級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)先級
第一個(gè)參數(shù)填需要更改優(yōu)先級的 Dispatch Queue,第二個(gè)參數(shù)填要與要指定的優(yōu)先級相同優(yōu)先級的 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ì)更長。
dispatch_time_t 表示的是一個(gè)時(shí)刻,可以由 dispatch_time()函數(shù)或者 dispatch_walltime()函數(shù)獲得
dispatch_time()函數(shù) 能夠獲取從第一個(gè)參數(shù)指定的時(shí)間開始,到第二個(gè)參數(shù)指定的納秒(毫微秒)后的時(shí)間 常用于計(jì)算相對時(shí)間
dispatch_walltime()函數(shù)由 POSIX 中使用的 struct timespec 類型的時(shí)間得到 dispatch_time_t 類型的值,常用計(jì)算絕對時(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 對象獲取 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ù)對已經(jīng)開始執(zhí)行的任務(wù)沒有影響,掛起后,追加到 Dispatch Queue 中,但尚未執(zhí)行的任務(wù)在此之后會(huì)暫停執(zhí)行(任務(wù)仍然可以繼續(xù)追加,但新追加的也會(huì)暫停執(zhí)行),而恢復(fù)則使得這些任務(wù)繼續(xù)執(zhí)行。
10. Dispatch Semaphore 信號量
Dispatch Semaphore 信號量在 GCD 常被用于進(jìn)行同步和控制并發(fā)。
信號量是一個(gè)整形值并且具有一個(gè)初始計(jì)數(shù)值,并且支持兩個(gè)操作:信號通知和等待。當(dāng)一個(gè)信號量被信號通知,其計(jì)數(shù)會(huì)被加1。當(dāng)在一個(gè)線程上設(shè)置一個(gè)信號量等待時(shí),線程會(huì)被阻塞至超時(shí)時(shí)間(如果有必要的話可以設(shè)置為一直阻塞),只有當(dāng)計(jì)數(shù)器大于零,計(jì)數(shù)才會(huì)被減1并且該線程恢復(fù)。
信號量可以被用來作為線程鎖,也可以用來控制并發(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í)行一次");
});