在上一篇中,咱們主要講了Dispatch Queue相關的內容。這篇主要講一下一些和實際相關的使用實例,Dispatch Groups和Dispatch Semaphore。安全
在咱們開發過程當中常常會用到在多少秒後執行某個方法,一般咱們會用這個- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
函數。不過如今咱們可使用一個新的方法。多線程
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_after(delayTime, dispatch_get_main_queue(), ^{ //do your task });
這樣咱們就定義了一個延遲2秒後執行的任務。不過在這裏有一點須要說明的是,不管你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
仍是dispatch_after
這個方法。並非說在你指定的延遲後當即運行,這些方法都是基於單線程的,它只是將你延遲的操做加入到隊列裏面去。因爲隊列裏面都是FIFO,因此必須在你這個任務以前的操做完成後纔會執行你的方法。這個延遲只是大概的延遲。若是你在主線程裏面調用這個方法,若是你主線程如今正在處理一個很是耗時的任務,那麼你這個延遲可能就會誤差很大。這個時候你能夠再開個線程,在裏面執行你的延遲操做。併發
//放到全局默認的線程裏面,這樣就沒必要等待當前調用線程執行完後再執行你的方法 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 });
這個想必你們都很是的熟悉,這個在單例初始化的時候是蘋果官方推薦的方法。這個函數能夠保證在應用程序中只執行指定的任務一次。即便在多線程的環境下執行,也能夠保證百分之百的安全。app
static id instance; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ //your init }); return instance; }
這裏面的predicate
必須是全局或者靜態對象。在多線程下同時訪問時,這個方法將被線程同步等待,直到指定的block執行完成。async
這個方法是執行循環次數固定的迭代,若是在併發的queue裏面能夠提升性能。好比一個固定次數的for循環函數
for (int i = 0; i < 1000; i ++) { NSLog(@"---%d---", i); }
若是隻是在一個線程裏面或者在一個串行的隊列中是同樣的,一個個執行。
如今咱們用dispatch_apply來寫這個循環:性能
dispatch_apply([array count], defaultQueue, ^(size_t i) { NSLog(@"----%@---", array[i]); }); NSLog(@"end");
這個方法執行後,它將像這個併發隊列中不斷的提交執行的block。這個i是從0開始的,最後一個是[array count] - 1
。測試
使用這個方法有幾個注意點:spa
end
。在實際開發中,咱們可能須要在一組操做所有完成後,才作其餘操做。好比上傳一組圖片,或者下載多個文件。但願在所有完成時給用戶一個提示。若是這些操做在串行化的隊列中執行的話,那麼你能夠很明確的知道,當最後一個任務執行完成後,就所有完成了。這樣的操做也並木有發揮多線程的優點。咱們能夠在併發的隊列中進行這些操做,可是這個時候咱們就不知道哪一個是最後一個完成的了。這個時候咱們能夠藉助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"); });
咱們首先建立一個group
而後往裏面加入咱們要執行的操做,在dispatch_group_notify
這個函數裏面添加所有完成的操做。上面代碼執行的時候,輸出的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
來表示添加任務,dispatch_group_leave
來表示有個任務已經完成了。用這個方法必定要注意必須成雙成對。
在多線程中一個比較重要的東西就是線程同步的問題。若是多個線程只是對某個資源只是讀的過程,那麼就不存在這個問題了。若是某個線程對這個資源須要進行寫的操做,那這個時候就會出現數據不一致的問題了。
__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); });
看看這個模擬的場景,咱們讓各個線程去訪問這個變量,其中有個操做是要修改這個變量。咱們把第一個操做先判斷有木有改變,而後故意延遲一下,這個時候咱們看下輸出結果:
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===
咱們能夠看到,再次判斷的時候,已經被修改了,若是咱們在實際的業務中這樣去判斷某些關鍵性的變量,可能就會出現嚴重的問題。下面看看咱們如何使用dispatch_barrier_async
來進行同步:
//併發隊列 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); });
如今看下輸出結果:
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-
如今咱們能夠發現操做4用dispatch_barrier_async
加入操做後,前面的操做3以前都操做完成以前這個strTest
都沒有變。然後面的操做都是改變後的值。這樣咱們的數據衝突的問題就解決了。
如今說明下這個函數乾的事情,當這個函數加入到隊列後,裏面block並非當即執行的,它會先等待以前正在執行的block所有完成後,才執行,而且在它以後加入到隊列中的block也在它操做結束後才能恢復以前的併發執行。咱們能夠把這個函數理解爲一條分割線,以前的操做,以後加入的操做。還有一個點要說明的是這個queue
必須是用dispatch_queue_create
建立出來的才行。
dispatch_semaphore_t
相似信號量,能夠用來控制訪問某一資源訪問數量。
使用過程:
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); });
這樣咱們同樣能夠保證,線程的數據安全。