iOS多線程——GCD篇,iosgcd
什麼是GCD
GCD是蘋果對多線程編程做的一套新的抽象基於C語言層的API,結合Block簡化了多線程的操作,使得我們對線程操作能夠更加的安全高效。
在GCD出現之前Cocoa架構提供了NSObject類的
performSelectorInBackground:withObject
performSelectorOnMainThread
方法來簡化多線程編程技術。
GCD可以解決以下多線程編程中經常出現的問題:
1.資料競爭(比如同時更新一個記憶體位址)
2.死結(互相等待)
3.太多線程導致消耗大量記憶體
在iOS中,如果把需要消耗大量時間的操作放在主線程上面,會妨礙主線程中被稱為RunLoop的主迴圈的執行,從而導致不能更新使用者介面、應用程式的畫面長時間停滯等問題。
Dispatch Queue
Dispatch Queue是GCD中對於任務的抽象隊列(FIFO)執行處理。
queue分為兩種,
SERIAL_DISPATCH_QUEUE 等待現在執行中處理結束
CONCURRENT_DISPATCH_QUEUE 不等待現在執行中處理結束
換句話說也就是 SERIAL_DISPATCH_QUEUE 是串列,CONCURRENT_DISPATCH_QUEUE是並行。
具體到線程上,就是SERIAL_DISPATCH_QUEUE只會創在一個線程來處理工作順序,而CONCURRENT_DISPATCH_QUEUE則會創在多個線程,但是具體建立多少個則是有啟動並執行作業系統根據資源決定的。
所以SERIAL_DISPATCH_QUEUE 中處理的代碼是有序的,而CONCURRENT_DISPATCH_QUEUE中則是無序的,但是相對會更高效一點。
API
dispatch_queue_create
用於建立一個任務執行queue
參數列表
const char *label queue的名稱,作為該queue的唯一標示,改名會在Xcode和Instruments的調試器中直接作為DispatchQueue名稱顯示出來
dispatch_queue_attr_t 設定queue的類型,即ConcurrentQueue還是SerialQueue,NULL則預設為SerialQueue
傳回值
dispatch_queue_t變數
這裡要說一下main_dispatch_queue 和 global_dispatch_queue 這兩種系統提供的,
main_queue通過
dispatch_get_main_queue()
global_queue通過
dispatch_get_global_queue(),global等級分為
HIGH、DEFAULT、LOW、BACKGROUND四種
dispatch_async
向指定的queue中添加block操作,非同步執行,屏蔽了多線程的實現細節,自動為我們產生線程執行。
dispatch_after
類似延遲函數,可以指定queue來進行延遲操作
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"等待3秒"); });
dispatch_group_notify
對於監聽queue的執行,當所有任務完成後可以進行回調操作
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"1"); }); dispatch_group_async(group, queue, ^{ NSLog(@"2"); }); dispatch_group_async(group, queue, ^{ NSLog(@"3"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"finish"); });
對於一系列的block在同一queue中執行,如果是serialQueue是順序進行的,因此可以在最後一個任務來處理結束操作。但是對於concurrentQueue是並行的,如果想監聽完結操作,就要用該方法。
dispatch_group_wait和notify差不多,只不過wait方法可以設定等待時間。如果時間到了還沒有結束queue的所有操作,那麼接下來還是會繼續進行,不過還是可以設定為forever一直等待下去,這樣就和notify起到一樣的作用。
dispatch_barrier_async
該操作主要是為了防止資源競爭。在concurrentQueue中,所有block無序的按照所建立的線程數量同時進行。如果在concurrentQueue中有兩個寫入操作,而且他都是讀取操作,這時兩個寫入操作間就會出現資源競爭,而讀取操作則會讀取髒資料。所以對於在concurrentQueue中不能夠與其它操作並行的block就需要使用dispatch_barrier_async方法來防止資源競爭。
dispatch_sync
和dispatch_async不同,dispatch_sync用於線程之間的同步操作,比如說A線程要做一件事必須要放在B線程之後來進行,那麼此時就需要用到dispatch_sync。
另外,不能夠在某個執行線程中同步自己,這樣會造成線程死結,比如說
dispatch_queue_t queue1 = dispatch_get_main_queue(); dispatch_sync(queue1, ^{ NSLog(@"main queue 中同步main queue操作"); }); dispatch_queue_t queue = dispatch_queue_create("com.queue.www", NULL); dispatch_async(queue, ^{ dispatch_sync(queue, ^{ NSLog(@"在新的serial queue中同步serial queue操作"); }); });
所以說使用serial queue的時候一定不要同步自己。
dispatch_apply
dispatch_apply函數是dispatch_sync和dispatch group的關聯函數,是用指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理執行結束,例如
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%ld",index); }); NSLog(@"apply finish");2015-08-02 09:38:18.296 Dispatch[7388:2035125] 42015-08-02 09:38:18.296 Dispatch[7388:2035244] 22015-08-02 09:38:18.296 Dispatch[7388:2035241] 12015-08-02 09:38:18.296 Dispatch[7388:2035259] 62015-08-02 09:38:18.296 Dispatch[7388:2035243] 02015-08-02 09:38:18.296 Dispatch[7388:2035257] 32015-08-02 09:38:18.296 Dispatch[7388:2035258] 52015-08-02 09:38:18.296 Dispatch[7388:2035260] 72015-08-02 09:38:18.296 Dispatch[7388:2035125] 82015-08-02 09:38:18.296 Dispatch[7388:2035244] 92015-08-02 09:38:18.296 Dispatch[7388:2035125] apply finish
實際上可以看出來,該函數讓主線程和queue進行同步操作,並且等queue中所有線程執行完畢後才繼續執行。
dispatch_semaphore
在進行資料處理時,dispatch_barrier_async可以避免這類問題,但是有時需要更加精細的操作。
比如要對數組添加10000個對象,用concurrentQueue添加。我們知道concurrentQueue會產生多個線程,很可能會出現多個線程一起對數組訪問的情況,很容易出現問題。我們需要控制一次只讓一個線程運算元組,如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); NSMutableArray *array = [NSMutableArray new]; for (int i = 0 ; i < 10000 ; i++) { dispatch_async(queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [array addObject:[NSNumber numberWithInt:i]]; NSLog(@"add %d",i); dispatch_semaphore_signal(semaphore); }); }
這裡簡單說一下訊號量,也就是建立dispatch_semaphore的第二個參數。指定一個訊號量,那麼當訊號量是大於0的時候所有線程都是可訪問的。一旦有現成訪問訊號量會減1,如果訊號量為0就會進入等待,知道dispatch_semaphore_signal函數調用來重新恢複訊號量。所以基本上可以理解為有幾個訊號量就能有幾個線程並發的訪問。
再比如說現在有兩個線程一個添加資料一個刪除資料,那麼就需要兩個訊號量變數來實現多線程間的協作
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphoreAdd = dispatch_semaphore_create(1); dispatch_semaphore_t semaphoreRemove = dispatch_semaphore_create(0); NSMutableArray *array = [NSMutableArray new]; for (int i = 0 ; i < 2 ; i++) { dispatch_async(queue, ^{ dispatch_semaphore_wait(semaphoreAdd, DISPATCH_TIME_FOREVER); [array addObject:[NSNumber numberWithInt:i]]; NSLog(@"add %lu",[array count]); dispatch_semaphore_signal(semaphoreRemove); }); dispatch_async(queue, ^{ dispatch_semaphore_wait(semaphoreRemove, DISPATCH_TIME_FOREVER); [array removeObject:[NSNumber numberWithInt:i]]; NSLog(@"add %lu",[array count]); dispatch_semaphore_signal(semaphoreAdd); }); }
dispatch_once
dispatch_once用來標記一個操作,只執行一次,該方法一般在生產單例對象使用。如果不用dispatch_once建立單例是不安全的,需要進行加鎖處理,但是dispatch_once可以很好地解決這一點。
+(instancetype)sharedInstance{ static CustomObject *obj; static dispatch_once_t once; dispatch_once(&once, ^{ obj = [[CustomObject alloc] init]; }); return obj;}