1.隊列git
最大的分類有兩種:串行和並行,對應的建立方法以下github
// 串行 dispatch_queue_t synQueue = dispatch_queue_create("syn", DISPATCH_QUEUE_SERIAL); // 並行 dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT);
上面的這種建立方式是自定義隊列,第一個參數是隊列標識,第二個參數是指定是哪一種隊列。面試
系統有自己的隊列,好比主隊列和全局隊列數組
// 主隊列 dispatch_get_main_queue(); // 全局隊列 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
主隊列是串行隊列,這也是爲何咱們網絡請求通常不在主隊列發起,由於這樣在請求返回前會阻塞當前隊列,沒法進行其餘操做;全局隊列是並行隊列,第一個參數是優先級,第二個參數是給蘋果預留的,通常爲0或NULL。網絡
因此通常面試問GCD有幾種隊列,通常是主隊列、全局隊列和自定義隊列。多線程
2.執行方法併發
串行執行方法是app
dispatch_sync(syncQueue, ^{});
並行執行方法是異步
dispatch_async(asyncQueue, ^{});
上面的寫法是,串行隊列調用串行方法,並行隊列調用並行方法,那若是交叉過來,串行隊列調用並行方法,並行隊列調用串行方法,執行順序會是如何呢?若是一個隊列前後調用串行和並行方法,又是怎樣執行的?async
測試代碼和過程我就不寫了,這裏先寫本身測試後的結論:
並行隊列並行執行,隨意輸出,不阻塞主線程
串行隊列串行執行,順序輸出,阻塞主線程
並行隊列串行執行,順序輸出,阻塞主線程
串行隊列並行執行,順序輸出,不阻塞主線程
Tip:串行和並行就像是單車道和多車道,單車道再運行多輛行駛,也只能每次經過一輛,多車道能夠多輛行駛,也能夠排隊行駛。
3.barrier(屏障、柵欄)
並行隊列並行執行的時候,全部任務都是隨時執行的,但若是其中一個任務很重要,需求中它將會影響後面隊列中的任務,該如何實現?GCD中有一個功能,這個功能相似於建立一個屏障、柵欄區域來隔開先後的任務,前面的任務執行完後,再執行柵欄區域的任務,才能執行後面的任務。
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_async(asyncQueue, ^{ NSLog(@"1"); }); dispatch_async(asyncQueue, ^{ NSLog(@"2"); }); dispatch_barrier_async(asyncQueue, ^{ NSLog(@"barrier"); }); dispatch_async(asyncQueue, ^{ NSLog(@"3"); }); dispatch_async(asyncQueue, ^{ NSLog(@"4"); });
輸出結果是1,2,barrier,3,4,也有可能會是2,1,barrier,4,3,但barrier必定會在前兩個任務執行完以後,後兩個任務執行以前執行的。
4.GCD和線程同步
若是多個線程訪問同一處代碼,那麼可能會出現問題,好比對同一個值的get和set方法,多線程的時候,因爲執行的時機是隨時的,因此咱們有可能訪問get方法獲取到的不是最新的值。一般是使用鎖來實現同步機制,比較經常使用是@synchronized或者NSLock及其子類來加鎖。
- (void)lockMethod { @synchronized(self) { //do something } }
以上這種寫法是根據給的對象,自動建立一個鎖,等到block總的代碼執行完,就釋放了鎖。可是以上的這個例子代碼裏,因爲鎖的對象是self,@synchronized的做用是保證此時沒有其餘線程對self對象進行訪問,這樣若是咱們在訪問加鎖程序的同時,就不能訪問其餘無關的代碼了,因此濫用@synchronized會下降代碼效率。
NSLock及其子類與@synchronized有一種缺陷,在極其極端的狀況下,同步塊會致使死鎖,另外,效率也不見得很高。
替代方案是使用GCD,示例代碼以下:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); - (NSString *)stringA { __block NSString* A; dispatch_async(asyncQueue, ^{ A = _localA; }); return A; } - (void)setStringA:(NSString *)A { dispatch_barrier_async(asyncQueue, ^{ _localA = A; }); }
上面的示例裏,全部的讀取A的操做,都被barrier屏蔽住,必須等賦值完後才能讀取,此時讀取到的A是最新的值。
5.任務組group
GCD能夠把任務分組,調用者會在回調函數中收到一組任務結束的通知。
任務組的建立:
dispatch_group_t group = dispatch_group_create();
一般的調用方法:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"1"); }); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"2"); }); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"3"); }); dispatch_group_notify(group, asyncQueue, ^{ NSLog(@"end"); });
等效於下面的這種方式,但區別是在任務完成前會阻塞:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"1"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"2"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"3"); dispatch_group_leave(group); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"end");
dispatch_group_enter(group)和dispatch_group_leave(group)必須成對出現,表示一個任務進入組、離開組,相似於引用計數的retain和release。dispatch_group_wait是用來阻塞線程的,第二個參數是時間參數,表示要阻塞多久,咱們用DISPATCH_TIME_FOREVER代表一直等,等到任務組都執行完才能向下執行其餘的。若是在調用enter以後,沒有對應的leave,那麼這一任務永遠執行不完,會由dispatch_group_wait一直阻塞着。
但這並非說dispatch_group_enter(group)和dispatch_group_leave(group)寫法太繁瑣,沒啥用。好比下面這段代碼
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"1"); dispatch_async(asyncQueue, ^{ sleep(3); NSLog(@"1-1"); }); }); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"2"); }); dispatch_group_async(group, asyncQueue, ^{ NSLog(@"3"); }); dispatch_group_notify(group, asyncQueue, ^{ NSLog(@"notify"); }); NSLog(@"end"); 結果: 2016-12-01 22:52:09.189 3 2016-12-01 22:52:09.189 2 2016-12-01 22:52:09.189 1 2016-12-01 22:52:09.189 end 2016-12-01 22:52:09.189 notify 2016-12-01 22:52:12.263 1-1
從結果能夠看到,group沒有處理任務1裏面的異步子任務,由於queue中的block是執行到末尾就返回的。若是要同步到其中的子任務,須要這樣改寫
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"1"); dispatch_async(asyncQueue, ^{ sleep(3); NSLog(@"1-1"); dispatch_group_leave(group); }); }); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"2"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(asyncQueue, ^{ NSLog(@"3"); dispatch_group_leave(group); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"end"); 結果: 2016-12-01 23:00:12.279 2 2016-12-01 23:00:12.279 3 2016-12-01 23:00:12.279 1 2016-12-01 23:00:15.350 1-1 2016-12-01 23:00:15.351 end
關鍵是把dispatch_group_enter和dispatch_group_leave放到合適的地方去。
思考:若是是一個任務組,執行兩個並行隊列的全部任務,會是如何?
6.循環執行
若是咱們碰上須要循環執行某些任務,好比遍歷一個數組作操做,又不想阻塞線程,該如何作呢?
用for循環這麼作:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); for (id obj in array) { dispatch_async(asyncQueue, ^{ // do something }); }
但若是再加個需求,在遍歷操做執行完後,才執行下一個任務,顯然上面的這個方法不合適,用串行又顯得不夠好。如今咱們能夠參考上面的group來實現:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); for (id obj in array) { dispatch_group_async(group, asyncQueue, ^{ // do something }); dispatch_group_notify(group, asyncQueue, ^{ // after end do something }); }
幸虧,對於循環,GCD提供了一個dispatch_apply函數來實現:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(10, asyncQueue, ^(size_t index) { NSLog(@"index %ld", index); }); NSLog(@"end"); 結果: index 1 index 0 index 3 index 2 index 4 index 5 index 6 index 7 index 8 index 9 end
可是dispatch_apply是同步方法,會阻塞線程,因此不要在主線程調用這個方法,並且index不是按順序輸出調用的。因而是對上面的代碼作一下調整:
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_async(asyncQueue, ^(){ dispatch_apply(10, asyncQueue, ^(size_t index){ NSLog(@"index %ld", index); }); NSLog(@"end"); });
7.單例
+ (id)shareInstance { static MyClass *myShareInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ myShareInstance = [[MyClass alloc] init]; }); return myShareInstance; }
8.延時delay
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (5.0 * NSEC_PER_SEC)); dispatch_after(time, dispatch_get_main_queue(), ^{ // do something });
示例代碼是延遲5秒。GCD的延遲方法比起performSelector的延遲方法好處是,代碼集中不分散,不用另外在寫執行方法,並且傳參數沒有限制,performSelector的參數必須是id類型。
9.優先級
調用全局隊列的時候,有個參數是優先級,但通常是默認優先級DISPATCH_QUEUE_PRIORITY_DEFAULT,一共有四個優先級,按順序是:高、默認、低、後臺。高優先級會先執行,但注意,在極端的狀況下會出現優先級反轉的狀況,低優先級的任務佔有資源致使高優先級任務沒法執行。
10.信號量
對於多個線程訪問同個資源,GCD還提供是一種解決方法,就是信號量dispatch_semaphore
dispatch_queue_t asynQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); for (int i = 0; i < 20; i++) { dispatch_async(asynQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"semaphore %@------ %i", semaphore, i); sleep(2); dispatch_semaphore_signal(semaphore); }); }
dispatch_semaphore_create(2)建立了一個總量爲2的信號量;
dispatch_semaphore_wait是等待信號,並讓信號量-1,若是獲取到的信號量是0,那麼根據設置的超時時間進行等待,例子裏設置的超時時間是一直;
dispatch_semaphore_signal是發送信號,並讓信號量+1;
這套信號機制是否是很相似引用計數。因此,上面的這段代碼併發了20個任務,每一個任務都會有sleep,但每執行2個任務,經由dispatch_semaphore_wait減了兩次,就爲0,其餘的任務只能等sleep後dispatch_semaphore_signal加回信號量才能執行,如此反覆。
能夠用來坐線程同步,好比主線程與新線程同步(場景像請求動態域名)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_queue_t asynQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(asynQueue, ^{ NSLog(@"asyn queue task"); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"asyn queue task finish and wait"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), asynQueue, ^{ dispatch_semaphore_signal(semaphore); }); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(semaphore); NSLog(@"main queue task"); 結果 2016-11-30 23:44:59.044 asyn queue task 2016-11-30 23:44:59.044 asyn queue task finish and wait 2016-11-30 23:45:02.045 main queue task
note:上面用的是串行執行,若是是並行執行的話,必須先sleep主線程保證新線程的優先執行。
11. dispatch_set_target_queue
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);
dispatch_set_target_queue有兩個做用,一個是繼承target_queue的優先級,固然,系統的queue的優先級是沒法用這個方法作修改的;第二個是能夠修改層級關係,好比下面這段代碼
dispatch_queue_t targetQueue = dispatch_queue_create("myTargetQueue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queueA = dispatch_queue_create("myQueueA", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queueB = dispatch_queue_create("myQueueB", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queueC = dispatch_queue_create("myQueueC", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(queueA, targetQueue); dispatch_set_target_queue(queueB, targetQueue); dispatch_set_target_queue(queueC, targetQueue); dispatch_async(queueA, ^{ NSLog(@"A-1"); }); dispatch_async(queueB, ^{ NSLog(@"B-1"); }); dispatch_async(queueC, ^{ NSLog(@"C-1"); }); dispatch_async(queueB, ^{ NSLog(@"B-2"); }); dispatch_async(queueA, ^{ NSLog(@"A-2"); }); dispatch_async(queueC, ^{ NSLog(@"C-2"); }); 結果: 2016-12-01 22:14:15.247 A-1 2016-12-01 22:14:15.248 A-2 2016-12-01 22:14:15.248 B-1 2016-12-01 22:14:15.248 B-2 2016-12-01 22:14:15.249 C-1 2016-12-01 22:14:15.249 C-2
原本每一個串行隊列的調用都是異步的,但通過dispatch_set_target_queue指定到目標的串行隊列後,執行順序就有了前後,每一個queue會先執行完其中全部任務,以後下一個queue才能執行,至關於按照queue的調用順序,把這些queue排了個序。
12. dispatch_suspend和dispatch_resume
dispatch_queue_t asyncQueue = dispatch_queue_create("asyn", DISPATCH_QUEUE_CONCURRENT); dispatch_async(asyncQueue, ^{ NSLog(@"1"); sleep(3); dispatch_resume(asyncQueue); }); dispatch_suspend(asyncQueue); dispatch_async(asyncQueue, ^{ NSLog(@"2"); }); 結果: 2016-12-01 23:29:41.170 1 2016-12-01 23:29:44.245 2
dispatch_suspend和dispatch_resume也是成對出現,不然會出現崩潰。
一段很神奇的代碼
dispatch_queue_t myQueue = dispatch_queue_create("123456", DISPATCH_QUEUE_SERIAL); dispatch_async(myQueue, ^{ NSLog(@"1"); sleep(3); dispatch_resume(myQueue); }); NSLog(@"1.5");// 最關鍵的一句,若是不加這個,1和2是沒法輸出的 dispatch_suspend(myQueue); dispatch_async(myQueue, ^{ NSLog(@"2"); }); NSLog(@"3");
我的猜想,加入queue的block任務,不會立馬執行,會在稍後判斷是否有任務的加入,或者其餘的針對queue操做,若是沒有,才執行block任務。因此上面的1.5的做用是使得判斷沒有針對queue的操做,因此執行block的任務。註釋掉1.5,會使得block還沒被執行,就被掛起。
13.dispatch_source_t timer
dispatch_source_t timer能夠作爲定時器來使用
@property (nonatomic, strong) dispatch_source_t timer; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); self.timer = timer; dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ NSLog(@"haha"); }); dispatch_resume(timer);
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);
dispatch_source_set_timer的第三個參數是指定間隔時間,第四個參數是指定能夠有多大偏差。
若是是指定時間啓動,那麼須要設置第二個參數dispatch_time_t start
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC);
咱們設置了執行的定時任務,也能夠取消,咱們把上面的代碼改寫一下
@property (nonatomic, strong) dispatch_source_t timer; __block int i = 0; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); self.timer = timer; dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ NSLog(@"haha"); i++; if (i == 3) { dispatch_source_cancel(timer); } }); dispatch_resume(timer); dispatch_source_set_cancel_handler(timer, ^{ NSLog(@"cancel"); }); 結果: 2016-12-04 15:27:41.080 haha 2016-12-04 15:27:44.144 haha 2016-12-04 15:27:47.121 haha 2016-12-04 15:27:47.121 cancel
NSTimer是咱們經常使用的定時器的類,可是它有兩個問題,一個是會持有target和userinfo,一個是計時不是太精準,由於它是跟着runloop走的。MSWeakTimer是一個處理定時器timer循環引用的,它就是使用了dispatch_source_t timer。
最後一些注意
執行異步方法時,是須要拷貝block的,因此若拷貝所須要的時間超過執行的時間,顯得效率下降,異步的效果得不償失。
不要調用dispatch_get_current_queue(),由於若是當前隊列正在執行同步方法,會引發死鎖。所幸這個方法蘋果已經廢棄了。
在非ARC項目中,dispatch_queue_create生成的線程須要手動dispatch_release,即手動管理dispatch_queue_t內存。
線程直到執行完全部提交到其中處理的block執行完,纔會釋放,在其中任何地方調用dispatch_release,都不會當即被釋放。
If your app isn’t using ARC, you should call dispatch_release on a dispatch queue when it’s no longer needed. Any pending blocks submitted to a queue hold a reference to that queue, so the queue is not deallocated until all pending blocks have completed.