iOS開發實戰小知識——GCD使用

今天下午就和GCD扛上了, 索性好好了解下。 原理及源碼不講, 有興趣的小夥伴本身去看吧。實際上是我看不懂。(先跑了)。。 今天主要說說平時的使用。。。 開篇兩個問題: dispatch_get_global_queue 與 dispatch_get_main_queue 什麼區別?? 串行和並行, 異步同步??? Dispatch Barrierdispatch_group_wait有啥區別??? 懵逼啊。。一步一步來看看吧。。。ios

GCD

Grand Central Dispatch(GCD)是Apple推出的一套多線程解決方案,它擁有系統級的線程管理機制,開發者不須要再管理線程的生命週期,只須要關注於要執行的任務便可.數據庫

1.dispatch_queue

操做是在多線程上仍是單線程主要是看隊列的類型和執行方法,並行隊列異步執行才能在多線程,並行隊列同步執行就只會在主線程執行了. dispatch_get_global_queue: //全局隊列,一個並行的隊列 dispatch_get_main_queue: //主隊列,主線程中的惟一隊列,一個串行隊列 dispatch_get_global_queue:用於獲取一個全局隊列. dispatch_get_main_queue:該API的使用主要是在更新UI時獲取dispatch_get_main_queue()並把任務提交到主隊列中. main queue設置了併發數爲1,即串行隊列,而且將targetq指向com.apple.root.default-overcommit-priority隊列。安全

隊列優先級有八個,分別爲低、默認、高、後臺以及對應的overcommit。枚舉定義以下:bash

enum {
    DISPATCH_ROOT_QUEUE_IDX_LOW_PRIORITY = 0,                //低優先級
    DISPATCH_ROOT_QUEUE_IDX_LOW_OVERCOMMIT_PRIORITY,         //低優先級+overcommit
    DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY,                //默認優先級
    DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY,     //默認優先級+overcommit
    DISPATCH_ROOT_QUEUE_IDX_HIGH_PRIORITY,                   //高優先級
    DISPATCH_ROOT_QUEUE_IDX_HIGH_OVERCOMMIT_PRIORITY,        //高優先級+overcommit
    DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_PRIORITY,             //後臺
    DISPATCH_ROOT_QUEUE_IDX_BACKGROUND_OVERCOMMIT_PRIORITY,  //後臺+overcomit
};
複製代碼

自定義隊列:網絡

//串行隊列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//並行隊列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
複製代碼

當咱們處理耗時操做時,好比讀取數據庫、請求網絡數據,爲了不這些耗時操做卡住UI,可將耗時任務放到子線程中,執行完成後再通知主線程更新UI,代碼示例以下:多線程

//耗時操做
    dispatch_async(dispatch_get_main_queue(), ^{
         //更新UI
        }); 
    });
複製代碼
2.Dispatch Barrier

會確保隊列中先於Barrier Block提交的任務都完成後再執行它,而且執行時隊列不會同步執行其它任務,等Barrier Block執行完成後再開始執行其餘任務。 當多線程併發讀寫同一個資源時,爲了保證資源讀寫的正確性,能夠用Barrier Block解決該問題。代碼示例以下:併發

//建立自定義並行隊列
dispatch_queue_t queue = dispatch_queue_create("com.gcdTest.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    //讀操做
});
dispatch_barrier_async(queue, ^{
    //barrier block,可用於寫操做
    //確保資源更新過程當中不會有其餘線程讀取
    NSLog(@"work2");
});
dispatch_async(queue, ^{
    //讀操做
    NSLog(@"work3");
});
複製代碼

這裏有個須要注意也是官方文檔上提到的一點,若是咱們調用dispatch_barrier_async時將Barrier blocks提交到一個global queue,barrier blocks執行效果與dispatch_async()一致;只有將Barrier blocks提交到使用DISPATCH_QUEUE_CONCURRENT屬性建立的並行queue時它纔會表現的如同預期app

dispatch_group_wait的使用舉例:less

dispatch_queue_t defultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t requestGroup = dispatch_group_create();
    
    dispatch_group_async(requestGroup, defultQueue, ^{
        sleep(2);
        NSLog(@"++++++++1111");
    });
    
    dispatch_group_async(requestGroup, defultQueue, ^{
        NSLog(@"+++++++++2222");
    });
    
    NSLog(@"++++++++please wait 1 2 \n");
    
    dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
    
    NSLog(@"++++++++++task 1 2 finished \n");
    
    dispatch_group_async(requestGroup, defultQueue, ^{
        NSLog(@"+++++++++333");
    });
    
    dispatch_group_async(requestGroup, defultQueue, ^{
        NSLog(@"+++++++++4444");
    });
    
    dispatch_group_wait(requestGroup, DISPATCH_TIME_FOREVER);
    NSLog(@"+++++++++task 3 4 finished \n");
複製代碼

打印結果:異步

2019-05-22 20:39:46.759798+0800 GearBest[84446:1389970] ++++++++please wait 1 2
2019-05-22 20:39:46.759844+0800 GearBest[84446:1390248] +++++++++2222
2019-05-22 20:39:46.759839+0800 GearBest[84446:1390277] ++++++++1111
2019-05-22 20:39:46.760051+0800 GearBest[84446:1389970] ++++++++++task 1 2  finished
2019-05-22 20:39:46.760182+0800 GearBest[84446:1390248] +++++++++4444
2019-05-22 20:39:46.760173+0800 GearBest[84446:1390277] +++++++++333
2019-05-22 20:39:46.760338+0800 GearBest[84446:1389970] +++++++++task 3 4 finished
複製代碼

以上即便加了睡眠,也是能夠保證先執行12,完成以後在執行34的。

我用Dispatch Barrier也實現了上述功能,代碼以下:

dispatch_queue_t defultQueue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(defultQueue, ^{
        sleep(2);
        NSLog(@"++++++++1111");
    });
    
    dispatch_async(defultQueue, ^{
        NSLog(@"+++++++++2222");
    });
    
    dispatch_barrier_async(defultQueue, ^{
        NSLog(@"++++++++++task 1 2 finished \n");
    });
    
    dispatch_async(defultQueue, ^{
        NSLog(@"+++++++++333");
    });
    dispatch_async(defultQueue, ^{
        NSLog(@"+++++++++4444");
    });

    dispatch_barrier_async(defultQueue, ^{
        NSLog(@"+++++++++task 3 4 finished \n");
    });
複製代碼

打印結果:

2019-05-22 20:49:51.266512+0800 GearBest[85906:1403037] +++++++++2222
2019-05-22 20:49:53.266866+0800 GearBest[85906:1403000] ++++++++1111
2019-05-22 20:49:53.267081+0800 GearBest[85906:1403000] ++++++++++task 1 2  finished
2019-05-22 20:49:53.267217+0800 GearBest[85906:1403036] +++++++++4444
2019-05-22 20:49:53.267217+0800 GearBest[85906:1403000] +++++++++333
2019-05-22 20:49:53.267339+0800 GearBest[85906:1403036] +++++++++task 3 4 finished
複製代碼

由上面得出的結論:Dispatch Barrier 和 dispatch_group_wait 能夠實現相同的效果,只是寫法不一樣。

dispatch_apply . 先執行完了 在執行下面的。 栗子:
// 注意這裏mainqueue 不行。
    dispatch_queue_t globaleQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globaleQueue, ^{
        dispatch_apply(5, globaleQueue, ^(size_t index) {
            NSLog(@"11111");
        });
        NSLog(@"apply");
    });
複製代碼

結果:

2019-05-27 23:21:12.505513+0800 TestCode[3782:396214] 11111
2019-05-27 23:21:12.505513+0800 TestCode[3782:396192] 11111
2019-05-27 23:21:12.505513+0800 TestCode[3782:396194] 11111
2019-05-27 23:21:12.505550+0800 TestCode[3782:396193] 11111
2019-05-27 23:21:12.505768+0800 TestCode[3782:396214] 11111
2019-05-27 23:21:12.506075+0800 TestCode[3782:396214] apply
複製代碼

? 直接用dispatch_group實現相似的功能:順序執行

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(@"1111111");
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(2);
        NSLog(@"2222222");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"33333333");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"都執行完了啊");
    });
複製代碼

打印結果:

2019-05-27 22:39:23.734921+0800 TestCode[2272:267297] 33333333
2019-05-27 22:39:23.734921+0800 TestCode[2272:267302] 1111111
2019-05-27 22:39:25.737724+0800 TestCode[2272:267300] 2222222
2019-05-27 22:39:25.738355+0800 TestCode[2272:267300] 都執行完了啊
複製代碼
3. dispatch_async

dispatch_async用來異步執行任務.dispatch_async封裝調用了dispatch_async_f函數,先將block拷貝到堆上,避免block執行前被銷燬,同時傳入_dispatch_call_block_and_release來保證block執行後會執行Block_release。

總結一下:dispatch_async的流程是用鏈表保存全部提交的block,而後在底層線程池中,依次取出block並執行;而向主隊列提交block則會向主線程的Runloop發送消息並喚醒Runloop,接着會在回調函數中取出block並執行。 dispatch_sync的邏輯主要是將任務放入隊列,並用線程專屬信號量作等待,保證每次只會有一個block在執行。

3.2 dispatch_sync 同步按順序執行任務。

稍有不慎 就會死鎖; 例如:

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"hello world?");
    });

//仍是死鎖。。 自建立queue 這樣也是死鎖。。
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"hello world?");
            });
        });
複製代碼

主線程中執行的block 等待主線程中執行的block執行結束。 因此死鎖了。

4. dispatch_group

dispatch_group能夠將GCD的任務合併到一個組裏來管理,也能夠同時監聽組裏全部任務的執行狀況. 實際使用狀況舉例:有多個網絡請求,請求一、2完成拿到結果以後,才能夠進行後面的請求。這時候就可使用dispatch_group。 栗子代碼:

dispatch_group_t requestGroup = dispatch_group_create();
    dispatch_group_enter(requestGroup);
    [self loadHeadDataCompletion:^{
        //請求1
        NSLog(@"+++++++111111");
        dispatch_group_leave(requestGroup);
    }];
    
    dispatch_group_enter(requestGroup);
    [self requestRecommandSearchAdvertiseDataCompletion:^{
        //請求2
        NSLog(@"+++++++2222");
        dispatch_group_leave(requestGroup);
    }];
    
    dispatch_group_notify(requestGroup, dispatch_get_main_queue(), ^{
        //拿到了請求12的*結果*以後,在進行請求3
        [self loadHomeGoodsList];
    });
複製代碼

dispatch_group有兩個須要注意的地方: 一、dispatch_group_enter必須在dispatch_group_leave以前出現 二、dispatch_group_enter和dispatch_group_leave必須成對出現

dispatch_group本質是個初始值爲LONG_MAX的信號量,等待group中的任務完成實際上是等待value恢復初始值。 dispatch_group_enter和dispatch_group_leave必須成對出現。 若是dispatch_group_enter比dispatch_group_leave多一次,則wait函數等待的 線程不會被喚醒和註冊notify的回調block不會執行; 若是dispatch_group_leave比dispatch_group_enter多一次,則會引發崩潰。

5.dispatch_once

dispatch_once能保證任務只會被執行一次,即便同時多線程調用也是線程安全的。經常使用於建立單例、swizzeld method等功能. dispatch_once用原子性操做block執行完成標記位,同時用信號量確保只有一個線程執行block,等block執行完再喚醒全部等待中的線程。

6.dispatch_source

dispatch_source主要用於定時器,比NSTimer精度高一點點吧。 其餘計時器參考:NSTimer NSTimer 到底準不許

參考文章: Grand-Central-Dispatch

GCD

相關文章
相關標籤/搜索