在上一篇中揽趾,我們主要講了Dispatch Queue相關(guān)的內(nèi)容。這篇主要講一下一些和實際相關(guān)的使用實例酸休,Dispatch Groups和Dispatch Semaphore乞巧。
dispatch_after
在我們開發(fā)過程中經(jīng)常會用到在多少秒后執(zhí)行某個方法口蝠,通常我們會用這個- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
函數(shù)器钟。不過現(xiàn)在我們可以使用一個新的方法。
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
//do your task
});
這樣我們就定義了一個延遲2秒后執(zhí)行的任務(wù)妙蔗。不過在這里有一點需要說明的是傲霸,無論你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
還是dispatch_after
這個方法。并不是說在你指定的延遲后立即運行眉反,這些方法都是基于單線程的昙啄,它只是將你延遲的操作加入到隊列里面去。由于隊列里面都是FIFO寸五,所以必須在你這個任務(wù)之前的操作完成后才會執(zhí)行你的方法梳凛。這個延遲只是大概的延遲。如果你在主線程里面調(diào)用這個方法梳杏,如果你主線程現(xiàn)在正在處理一個非常耗時的任務(wù)韧拒,那么你這個延遲可能就會偏差很大。這個時候你可以再開個線程十性,在里面執(zhí)行你的延遲操作叭莫。
//放到全局默認(rèn)的線程里面,這樣就不必等待當(dāng)前調(diào)用線程執(zhí)行完后再執(zhí)行你的方法
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do your task
});
dispatch_once
這個想必大家都非常的熟悉烁试,這個在單例初始化的時候是蘋果官方推薦的方法。這個函數(shù)可以保證在應(yīng)用程序中只執(zhí)行指定的任務(wù)一次拢肆。即使在多線程的環(huán)境下執(zhí)行减响,也可以保證百分之百的安全靖诗。
static id instance;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
//your init
});
return instance;
}
這里面的predicate
必須是全局或者靜態(tài)對象。在多線程下同時訪問時支示,這個方法將被線程同步等待刊橘,直到指定的block執(zhí)行完成。
dispatch_apply
這個方法是執(zhí)行循環(huán)次數(shù)固定的迭代颂鸿,如果在并發(fā)的queue里面可以提高性能促绵。比如一個固定次數(shù)的for循環(huán)
for (int i = 0; i < 1000; i ++) {
NSLog(@"---%d---", i);
}
如果只是在一個線程里面或者在一個串行的隊列中是一樣的,一個個執(zhí)行嘴纺。
現(xiàn)在我們用dispatch_apply來寫這個循環(huán):
dispatch_apply([array count], defaultQueue, ^(size_t i) {
NSLog(@"----%@---", array[i]);
});
NSLog(@"end");
這個方法執(zhí)行后败晴,它將像這個并發(fā)隊列中不斷的提交執(zhí)行的block。這個i是從0開始的栽渴,最后一個是[array count] - 1
尖坤。
使用這個方法有幾個注意點:
- 這個方法調(diào)用的時候會阻塞當(dāng)前的線程,也就是上面的循環(huán)全部執(zhí)行完畢后闲擦,才會輸出
end
慢味。 - 在你使用這個任務(wù)進(jìn)行操作的時候,你應(yīng)該確保你要執(zhí)行的各個任務(wù)是獨立的墅冷,而且執(zhí)行順序也是無關(guān)緊要的纯路。
- 在你使用這個方法的時候,你還是要權(quán)衡下整體的性能的寞忿,如果你執(zhí)行的任務(wù)時間比線程切換的時間還短驰唬。那就得不償失了。
dispatch_group
在實際開發(fā)中罐脊,我們可能需要在一組操作全部完成后定嗓,才做其他操作。比如上傳一組圖片萍桌,或者下載多個文件宵溅。希望在全部完成時給用戶一個提示。如果這些操作在串行化的隊列中執(zhí)行的話上炎,那么你可以很明確的知道恃逻,當(dāng)最后一個任務(wù)執(zhí)行完成后,就全部完成了藕施。這樣的操作也并木有發(fā)揮多線程的優(yōu)勢寇损。我們可以在并發(fā)的隊列中進(jìn)行這些操作,但是這個時候我們就不知道哪個是最后一個完成的了裳食。這個時候我們可以借助dispatch_group:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, defaultQueue, ^{
//task1
NSLog(@"1");
});
dispatch_group_async(group, defaultQueue, ^{
//task2
NSLog(@"2");
});
dispatch_group_async(group, defaultQueue, ^{
//task3
NSLog(@"3");
});
dispatch_group_async(group, defaultQueue, ^{
//task4
NSLog(@"4");
});
dispatch_group_async(group, defaultQueue, ^{
//task5
NSLog(@"5");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"finish");
});
我們首先創(chuàng)建一個group
然后往里面加入我們要執(zhí)行的操作矛市,在dispatch_group_notify
這個函數(shù)里面添加全部完成的操作。上面代碼執(zhí)行的時候诲祸,輸出的1浊吏,2而昨,3,4找田,5的順序是不一定的歌憨,但是輸出的finish一定是在1,2墩衙,3务嫡,4,5之后漆改。
對于添加到group的操作還有另外一個方法:
dispatch_group_enter(group);
dispatch_group_enter(group);
dispatch_async(defaultQueue, ^{
NSLog(@"1");
dispatch_group_leave(group);
});
dispatch_async(defaultQueue, ^{
NSLog(@"2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"finish");
});
我們可以用dispatch_group_enter
來表示添加任務(wù)心铃,dispatch_group_leave
來表示有個任務(wù)已經(jīng)完成了。用這個方法一定要注意必須成雙成對籽懦。
線程同步
在多線程中一個比較重要的東西就是線程同步的問題于个。如果多個線程只是對某個資源只是讀的過程,那么就不存在這個問題了暮顺。如果某個線程對這個資源需要進(jìn)行寫的操作厅篓,那這個時候就會出現(xiàn)數(shù)據(jù)不一致的問題了。
使用dispatch_barrier_async
__block NSString *strTest = @"test";
dispatch_async(defaultQueue, ^{
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
});
dispatch_async(defaultQueue, ^{
NSLog(@"--%@--3-", strTest);
});
dispatch_async(defaultQueue, ^{
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
});
看看這個模擬的場景捶码,我們讓各個線程去訪問這個變量羽氮,其中有個操作是要修改這個變量。我們把第一個操作先判斷有木有改變惫恼,然后故意延遲一下档押,這個時候我們看下輸出結(jié)果:
2015-01-03 15:42:21.351 測試[1652:60015] --test--3-
2015-01-03 15:42:21.351 測試[1652:60013] --modify--4-
2015-01-03 15:42:21.351 測試[1652:60014] --test--1-
2015-01-03 15:42:22.355 測試[1652:60014] ====changed===
我們可以看到,再次判斷的時候祈纯,已經(jīng)被修改了令宿,如果我們在實際的業(yè)務(wù)中這樣去判斷某些關(guān)鍵性的變量,可能就會出現(xiàn)嚴(yán)重的問題腕窥。下面看看我們?nèi)绾问褂?code>dispatch_barrier_async來進(jìn)行同步:
//并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
__block NSString *strTest = @"test";
dispatch_async(concurrentQueue, ^{
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
});
dispatch_async(concurrentQueue, ^{
NSLog(@"--%@--3-", strTest);
});
dispatch_barrier_async(concurrentQueue, ^{
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"--%@--5-", strTest);
});
現(xiàn)在看下輸出結(jié)果:
2015-01-03 16:00:27.552 測試[1786:65947] --test--1-
2015-01-03 16:00:27.552 測試[1786:65965] --test--3-
2015-01-03 16:00:29.553 測試[1786:65947] --test--2-
2015-01-03 16:00:29.553 測試[1786:65947] --modify--4-
2015-01-03 16:00:29.553 測試[1786:65947] --modify--5-
現(xiàn)在我們可以發(fā)現(xiàn)操作4用dispatch_barrier_async
加入操作后粒没,前面的操作3之前都操作完成之前這個strTest
都沒有變。而后面的操作都是改變后的值簇爆。這樣我們的數(shù)據(jù)沖突的問題就解決了癞松。
現(xiàn)在說明下這個函數(shù)干的事情,當(dāng)這個函數(shù)加入到隊列后入蛆,里面block并不是立即執(zhí)行的响蓉,它會先等待之前正在執(zhí)行的block全部完成后,才執(zhí)行哨毁,并且在它之后加入到隊列中的block也在它操作結(jié)束后才能恢復(fù)之前的并發(fā)執(zhí)行枫甲。我們可以把這個函數(shù)理解為一條分割線,之前的操作,之后加入的操作言秸。還有一個點要說明的是這個queue
必須是用dispatch_queue_create
創(chuàng)建出來的才行软能。
使用Dispatch Semaphore
dispatch_semaphore_t
類似信號量,可以用來控制訪問某一資源訪問數(shù)量。
使用過程:
- 先創(chuàng)建一個Dispatch Semaphore對象举畸,用整數(shù)值表示資源的可用數(shù)量
- 在每個任務(wù)中,調(diào)用dispatch_semaphore_wait來等待
- 獲得資源就可以進(jìn)行操作
- 操作完后調(diào)用dispatch_semaphore_signal來釋放資源
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block NSString *strTest = @"test";
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if ([strTest isEqualToString:@"test"]) {
NSLog(@"--%@--1-", strTest);
[NSThread sleepForTimeInterval:1];
if ([strTest isEqualToString:@"test"]) {
[NSThread sleepForTimeInterval:1];
NSLog(@"--%@--2-", strTest);
} else {
NSLog(@"====changed===");
}
}
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"--%@--3-", strTest);
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
strTest = @"modify";
NSLog(@"--%@--4-", strTest);
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"--%@--5-", strTest);
dispatch_semaphore_signal(semaphore);
});
這樣我們一樣可以保證凳枝,線程的數(shù)據(jù)安全抄沮。