前言:
前天學了IOS的NSOperation基本使用,我們得知NSOperation也是基於IOS GCD(Grand Central Dispatch)實現,其實在做IOS開發中GCD已經基本上能夠滿足大部分需求。作為IOS開發工程師很有必要對GCD做個全面了解,今天一邊寫demo一邊對比總結一下GCD使用。
了解GCD
GCD是Grand Central Dispatch的簡稱,它是基於C語言的。如果使用GCD,完全由系統管理線程,我們不需要編寫線程代碼。只需定義想要執行的任務,然后添加到適當的調度隊列(dispatch queue)。GCD會負責創建線程和調度你的任務,系統直接提供線程管理。
執行方式
使用線程就離不開線程隊列的執行方式和任務的執行方式,大致分以下幾個:
-
串行(Serial):讓任務一個接着一個地執行(一個任務執行完畢后,再執行下一個任務)
-
並行(Concurrent):可以讓多個任務並發(同時)執行(自動開啟多個線程同時執行任務)並發功能只有在異步(dispatch_async)函數下才有效。
-
同步(Synchronous):在當前線程中執行任務,不具備開啟新線程的能力
-
異步(Asynchronous):在新的線程中執行任務,具備開啟新線程的能力
調度隊列
上面了解到Dispatch 通過分發開發者提供的不同queue來調度任務,我們來看下GCD有哪些隊列。
隊列類型 | 創建方式 |
主線程串行隊列(mian) |
dispatch_get_main_queue(); |
自定義串行隊列(Serial) | dispatch_queue_create("MySerialQueue", DISPATCH_QUEUE_SERIAL); |
自定義並行隊列(Concurrent) |
dispatch_queue_create("MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); |
全局並行隊列(global) |
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); |
添加執行任務的方式大致有兩種:dispatch_sync 同步執行任務函數,不會開啟新的線程,dispatch_async 異步執行任務函數,會開啟新的線程,接下來分別看下他們的使用示例;
主線程串行隊列(mian)
dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(mainQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(mainQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
注意:
主線程串行隊列執行在主線程中,一般使用都是調用dispatch_async添加任務,使用dispatch_sync添加任務會導致死鎖問題。
運行結果:
通過運行結果可以得知,雖然任務是通過dispatch_async添加執行的,但是並沒有創建子線程去執行任務,而是執行在主線程中。
自定義串行隊列(Serial)
有時我們需要創建一個串行任務並且需要執行在子線程中,這時就需要創建串行隊列,進行異步添加調用方式執行任務。
dispatch_queue_t mySerialQueue = dispatch_queue_create("MySerialQueue", DISPATCH_QUEUE_SERIAL); dispatch_async(mySerialQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(mySerialQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(mySerialQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
運行結果:
可以看出串行隊列異步執行創建了一個線程,並且是依次執行。
全局並行隊列(global)
相對與串行任務隊列,有時我們需要同時執行多個任務,這個時候我們就需要使用並行任務隊列了,這里我們采用全部並行隊列。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(globalQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(globalQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(globalQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
這里可以根據不同的優先級創建不同的全部任務隊列
DISPATCH_QUEUE_PRIORITY_HIGH //高優先級
DISPATCH_QUEUE_PRIORITY_DEFAULT //默認優先級
DISPATCH_QUEUE_PRIORITY_LOW //低優先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND //后台執行
運行結果:
三個任務幾乎是同時進行的,而且動態為每個任務開辟了一個線程用於執行任務。並行隊列盡量使用異步添加任務的方式調用,同步添加任務方式調用不會創建子線程而是任務全部同時執行在主線程中導致UI卡死。
自定義並行隊列(Concurrent)
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(myConcurrentQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(myConcurrentQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(myConcurrentQueue, ^{ //在block里寫要執行的任務(代碼) NSLog(@"currentThread3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
運行結果和全部並行隊列類似。
調度隊列組
有時無論我們串行執行多任務還是並行執行多任務,需要這個任務組全部執行完畢之后通知我們,這里就需要用到隊列組了。
dispatch_group_t group = dispatch_group_create(); dispatch_queue_t myConcurrentQueue = dispatch_queue_create("MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); //dispatch_group_async用於把不同的任務歸為一組 //dispatch_group_notify當指定組的任務執行完畢之后,執行給定的任務 dispatch_group_async(group, myConcurrentQueue, ^{ NSLog(@"currentThread-1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_group_async(group, myConcurrentQueue, ^{ NSLog(@"currentThread-2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_group_async(group, myConcurrentQueue, ^{ NSLog(@"currentThread-3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_group_notify(group, myConcurrentQueue, ^{ NSLog(@"currentThread-g:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
運行結果:
GCD數據同步問題
既然是多任務執行,就難以避免會出現多任務同時訪問同一數據的問題,就會遇到同步的問題,串行隊列不太會遇見數據同步的問題,但是並行隊列一定會有數據同步的問題,IOS GCD考慮的很全面通過調用dispatch_barrier_async函數添加任務來隔離其他的任務,起到一個柵欄的作用,它等待所有位於barrier函數之前的操作執行完畢后執行,並且在barrier函數執行之后,barrier函數之后的操作才會得到執行,該函數需要同dispatch_queue_create函數生成的concurrent Dispatch Queue隊列一起使用。
dispatch_queue_t conCurrentQueue = dispatch_queue_create("myConCurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(conCurrentQueue, ^{ NSLog(@"currentThread-1:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"currentThread-2:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_barrier_async(conCurrentQueue, ^{ [NSThread sleepForTimeInterval:1.0]; NSLog(@"currentThread-b:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); [NSThread sleepForTimeInterval:1.0]; }); dispatch_async(conCurrentQueue, ^{ NSLog(@"currentThread-3:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); }); dispatch_async(conCurrentQueue, ^{ NSLog(@"currentThread-4:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
運行結果:
GCD其他常用函數
dispatch_once保證在app運行期間,block中的代碼只執行一次
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"只執行一次"); //這個block里的代碼,在程序執行過程中只會執行一次。 //比如在這里些單例的初始化 // static YourClass *instance = nil; // instance = [[YourClass alloc] init]; });
dispatch_after延時添加到隊列
double delayInSeconds = 3.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ //dispatch_after函數是延遲執行某個任務,任務既可以在mainQueue中進行也可以在其他queue中進行.既可以在serial隊列里執行也可以在concurrent隊列里執行。 NSLog(@"currentThread:%@ isMainThread:%@",[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
dispatch_apply把一項任務提交到隊列中多次執行,具體是並行執行還是串行執行由隊列本身決定
NSArray *array = [NSArray arrayWithObjects:@"who",@"is",@"lcj", nil]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply([array count], queue, ^(size_t index) { NSLog(@"%@ : currentThread:%@ isMainThread:%@",[array objectAtIndex:index],[NSThread currentThread],[[NSThread currentThread] isMainThread]?@"YES":@"NO"); });
總結:
用了GCD之后更見佩服Apple公司了,封裝的sdk 真是太完善了。簡單的總結了如何使用GCD,希望在以后的使用中有個大致了解。