GCD介紹
Grand Central Dispatch (GCD)是Apple開發的一個多核編程的解決方法。
GCD 以block為基本單位,一個block中的代碼可以為一個任務。下文中提到任務可以理解為執行某個block
GCD有兩大重要概念,分別是隊列和執行方式;使用block的過程,概括來說就是把block放進合適的隊列,并選擇合適的執行方式去執行block的過程。
GCD有三種隊列(遵循的是先進先出,FIFO):
- 串行隊列(每次只執行一個任務)
- 并發隊列 (可以形成多個任務并發)
- 主隊列 (這是一個特殊的串行隊列,而且隊列中的任務 一定會在主線程中執行)
兩種執行方式:
- 同步執行
- 異步執行
死鎖
造成死鎖的原因是:GCD遵循FIFO的原則,所以如果在同一線程,同一隊列中出現:后添加的任務需要先執行,而需要執行的任務卻只能等后添加的任務執行完之后才能執行,便發生了死鎖
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
以上的任務均發生在主線程里,首先在主隊列里添加任務1 任務3 當執行到同步線程這里,同步線程將任務2也添加到主隊列里,并且根據FIFO原則,需要等已經在主隊列的任務3執行完之后才能執行任務2,但是由于是同步的原因,想要執行任務3又必須等任務2執行之后,才能執行任務3 ,這樣任務2,3處于相互等待的僵局中,變造成了死鎖
GCD的常見應用:
- GCD定時器(不受RunLoop約束,比NSTimer更加準時)
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
- 單例模式(線程安全,使用簡單 )
static dispatch_once_t once;
dispatch_once($once, ^{
});
- 延時調用(注意僅表示在指定時間后提交任務 ,而非執行任務。如果任務提交到主隊列,它將在main runloop中執行,對于每隔1/60秒執行一個的RunLoop,任務最多可能在1+1/60秒后執行)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
- GCD任務組(注意:dispatch_group_notify是異步的,dispatch_group_wait是同步的)
dispatch_queue_t dispatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"任務A");
});
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"任務B");
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});
GCD的進階應用
- dispatch_suspend和dispatch_resume
這兩個是類似NSOperationQueue的暫停suspend和恢復resume
這些函數不會影響到隊列中已經執行的任務,隊列暫停后,已經添加到隊列中但是還沒有執行的任務 不會執行,直到隊列被恢復
dispatch_suspend(dispatchQueue);
dispatch_resume(dispatchQueue);
- dispatch_barrier_async
這個是為了解決 在寫入數據時,不能再其他線程讀取或寫入數據。
dispatch_barrier_async會把隊列的運行周期分為這三個過程:
1.首先等目前追加到并行隊列中所有任務都執行完成
2.開始執行dispatch_barrier_async中的任務這時候即便向并行隊列提交任務,也不會執行
3.dispatch_barrier_async中任務執行完成后,并行隊列恢復正常。
dispatch_barrier_async起到了承上啟下的作用。它保證此前的任務都先于自己執行,此后的任務也遲于自己執行。正如barrier的含義一樣,它起到一個柵欄或者分水嶺的作用
dispatch_queue_t dispatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, reading1)
dispatch_async(queue, reading2)
//那么可以用dispatch_barrier_async 來進行寫入,這樣實現承上啟下的作用
dispatch_barrier_async(queue, writing)
dispatch_async(queue, reading3)
dispatch_async(queue, reading4)
- dipactch_semaphore
信號量的使用,簡單來講就是 為0 需要等待, 不為0執行任務,并減1
類似于單例的操作,保證線程安全,并且可以通過信號量來控制并發隊列執行數量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);//初始化信號量為 5 , create()便是初始化信號量,()里是信號量的值,通過初始值來控制任務的并發數量.利用串行隊列來保證任務順序執行,利用并行隊列來保證任務是同時進行的
dispatch_queue_t diapatchQueue = dispatch_queue_create("xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("xx.queue",DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 100000; i++) {
dispatch_async(serialQueue, ^{
/*
某個線程執行到這里,如果信號量為1,那么wait方法返回1,開始執行接下來的操作。與此同時,因為信號量
變為0,其他執行到這里的線程必須等待
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//這個的作用是一直等待,直到信號量的值大于等于1,當這個方法執行后,會將信號量參數的值減1,減到0的時候,其他任務會進入等待狀態
dispatch_async(diapatchQueue, ^{//通過并行隊列保證任務同行進行
NSLog(@"thread-info:%@開始執行任務%d",[NSThread currentThread],(int)i);
sleep(1);
NSLog(@"thread-info:%@結束執行任務%d",[NSThread currentThread],(int)i);
/*
操作執行結束,記得要調用signal方法,把信號量的值加1.這樣,如果有別的線程在等待wait函數返回,就由最先等待的線程執行
*/
dispatch_semaphore_signal(semaphore);//將信號量的值加1,確保下次可以繼續執行
});
});
}
- dispatch_set_target_queue
作用是改變queue的優先級,一般都是把一個任務放到一個串行的queue中,如果這個任務被拆分了,被放置到多個串行的queue中,但實際還是需要這個任務同步執行,那么就會有問題,因為多個串行queue之間是異步進行的。
如果將多個串行的queue使用dispatch_set_target_queue指定到了同一目標,那么著多個串行queue在目標queue上就是同步執行的,不再是異步執行。
參考文章多線程