GCD源碼分析

  • 常見數據結構

    • dispatch_object_t
      GCD全部類的基類 image.png 從源碼能夠看出dispatch_object_t是一個聯合體(並且是一個透明聯合體),就是dispatch_object_t能夠是結構體中的任意一種類型,大小爲這些數據類型中最大的數據類型的大小
    • _os_object_s
      相似於類中的isa指針 image.png
    • dispatch_object_s
      GCD最基礎的基類 image.png
    • dispatch_continuation_s
      dispatch_continuation_s結構體主要封裝blockfunctiondispatch_async中的block最終都會封裝成這個數據類型 image.png
    • dispatch_group_s
      image.png
    • dispatch_queue_s
      image.png
  • 建立隊列源碼分析

    • dispatch_queue_create源碼
      第一步下一個符號斷點 image.png 發現源碼在libdispatch.dylib庫中,而後下載libdispatch.dylib源碼搜索dispatch_queue_create能夠找到image.png其實衝堆棧信息中也能夠知道接下來調用了_dispatch_lane_create_with_target函數來實現建立
    • _dispatch_lane_create_with_target源碼
      image.png 大體步驟以下:
      1. 規範化參數
      2. 設置目標隊列
        調用_dispatch_get_root_queue方法獲取目標隊列
        若是是串行隊列,則使用_dispatch_get_root_queue(0, true)函數獲取目標隊列,獲取到的目標隊列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_OVERCOMMIT_PRIORITY]
        若是是併發隊列,則使用_dispatch_get_root_queue(0, false)函數獲取目標隊列,獲取到的目標隊列是_dispatch_root_queues[DISPATCH_ROOT_QUEUE_IDX_DEFAULT_PRIORITY]
      3. 開闢空間建立隊列
        _dispatch_object_alloc開闢空間建立隊列
      4. 初始化隊列
        _dispatch_queue_init初始化隊列,這裏根據第一步規範後的參數判斷設置最大併發數,DISPATCH_QUEUE_WIDTH_MAX從這裏也能夠知道最大的併發數是4094
        設置隊列標識符等
      5. 賦值目標隊列
        將上述設置的目標隊列賦值
      具體流程圖以下:順序時間軸.jpg
    • 隊列和線程之間的關係圖
    image.png
  • 函數源碼分析

    • 異步函數分析
      • _dispatch_continuation_init任務包裝函數
        主要是copy任務,賦值func、ctxt等參數 image.png 經過_dispatch_continuation_init_f函數賦值func、ctxt等參數 image.png
      • _dispatch_continuation_async併發處理函數
        image.png 全局搜索dx_push發現是個宏#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z),發現仍是調用了dq_push方法繼續全局搜索dq_pushiShot2021-04-08 09.15.36.png發現dq_push方法有不少個不過每一個上面有類型不難猜想異步函數應該對應的是queue_concurrent下的方法對應的方法是_dispatch_lane_concurrent_push這裏能夠經過下符號斷點來驗證首先如今異步函數錢下個斷點image.png而後再往下走發現斷在了_dispatch_lane_concurrent_push的方法堆棧上因此也驗證了上述猜測image.png,繼續往下跟,全局搜索_dispatch_lane_concurrent_pushimage.png任務執行即出隊因此通常並行隊列中的異步任務的話都是空的因此若是不添加柵欄函數則通常會執行_dispatch_continuation_redirect_push方法這裏也能夠經過添加符號斷點來驗證image.pngimage.pngimage.pngimage.png發現的確_dispatch_lane_concurrent_push方法執行完成以後直接進入到_dispatch_continuation_redirect_push方法而後就執行了任務繼續全局搜索_dispatch_continuation_redirect_push方法image.png此時的dq_push對應的就不是_dispatch_lane_concurrent_push方法了由於對應的隊列類型變成了跟隊列,因此此時應該對應的是_dispatch_root_queue_push方法一樣的也能夠根據上述的步驟下一個符號斷點來驗證,繼續全局搜索_dispatch_root_queue_push方法image.png下個_dispatch_root_queue_push_override符號斷點在下一個_dispatch_root_queue_push_inline符號斷點發現會走到_dispatch_root_queue_push_override方法中而後image.png裏面又繼續調用了_dispatch_root_queue_push_inline方法image.png繼續查找_dispatch_root_queue_poke方法image.png全局搜索_dispatch_root_queue_poke_slow方法iShot2021-04-08 11.10.05.png到這裏異步函數建立線程的流程就已經完成以下圖未命名文件(29).jpg可是此時僅僅是建立了線程,可是任務尚未執行此時再看上方的_dispatch_root_queues_init方法點進去發現發現是dispatch_once_f方法調用image.png傳入的func_dispatch_root_queues_init_onceiShot2021-04-08 14.40.24.png,繼續進到_dispatch_worker_thread2(線程的執行是系統底層去出發的,這裏只須要綁定線程,系統會去觸發線程)方法查看源碼image.png_dispatch_root_queue_drain方法源碼image.pngimage.pngimage.png最終發如今這裏調用了任務方法,分析以前徹底能夠打斷點而後打印堆棧調用狀況而後再去源碼中找對應代碼,堆棧調用狀況以下:image.png發現堆棧的調用狀況就和上述分析的流程一致,
        總流程圖以下:未命名文件(30).jpg
    • 同步函數分析
      首先看dispatch_sync源碼image.png繼續看_dispatch_sync_f源碼image.pngimage.png發現同步函數的地方就是經過柵欄函數來實現的(柵欄函數源碼以下)
  • 柵欄函數源碼分析

    • 同步柵欄函數必須本身的block任務執行完成,下面的任務block纔會執行,這就表示同步阻塞的是當前的線程
    • 異步柵欄函數不須要等本身的任務block執行,下面的代碼會接着執行,這就表示異步阻塞的是隊列(queue)。
    注意:柵欄函數只能用在併發自定義隊列中,全局併發隊列,若是堵塞了其餘系統任務,會形成其餘任務,串行隊列使用柵欄函數等於沒用
    dispatch_barrier_sync源碼以下:image.pngimage.pngimage.pngimage.png 同步柵欄函數源碼流程:未命名文件(31).jpg
  • 死鎖源碼分析

    上述柵欄函數分析得知死鎖在_dispatch_sync_f_slow方法中處理的image.png首先將當前任務添加到隊列中而後執行__DISPATCH_WAIT_FOR_QUEUE__方法image.png再看_dq_state_drain_locked_by方法源碼image.pngimage.png判斷dq是否爲正在等待的隊列,而後給出一個狀態state,而後將dq的狀態和當前任務依賴的隊列進行匹配,匹配成功則拋出異常
  • 單列源碼分析

    全局搜索dispatch_once查看源碼image.png同時發現_dispatch_Block_invoke就是一個宏#define _dispatch_Block_invoke(bb) \ ((dispatch_function_t)((struct Block_layout *)bb)->invoke)就是invoke方法image.pngimage.pngimage.pngimage.pngimage.png 單例底層實現流程圖未命名文件(31).jpg
  • 信號量

    控制最大併發量(通常使用很少)核心函數以下:
    1. dispatch_semaphore_create 建立信號量,指定最大併發數
    2. dispatch_semaphore_signal 發送信號
    3. dispatch_semaphore_wait 等待信號
    代碼示例以下:
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_semaphore_t sem = dispatch_semaphore_create(2);
        //任務1
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            NSLog(@"執行任務1");
            sleep(1);
            NSLog(@"任務1完成");
            dispatch_semaphore_signal(sem);
        });
        //任務2
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            NSLog(@"執行任務2");
            sleep(1);
            NSLog(@"任務2完成");
            dispatch_semaphore_signal(sem);
        });
        //任務3
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
            NSLog(@"執行任務3");
            sleep(1);
            NSLog(@"任務3完成");
            dispatch_semaphore_signal(sem);
        });
    }
    複製代碼
    打印結果以下:image.png能夠發現由於控制最大的併發量是2因此能同時執行的任務有兩個,此時任務一和任務2是同時執行的,任務三是等到前兩個任務執行完成以後再執行的
    源碼分析:
    1. dispatch_semaphore_create
      主要是初始化工做,設置最大併發量(最大併發量要大於0),封裝成dispatch_semaphore_t結構體 image.png
    2. dispatch_semaphore_wait
      主要是對併發量進行--操做,若是當前的併發量已經小於0了說明當前已經達到了最大併發量,因此任務進行無限等待狀態(阻塞線程)源碼以下image.png os_atomic_dec2o是個宏,由於裏面是一個一個宏嵌套,因此這裏一個一個剖析後以下;
      os_atomic_dec2o(dsema, dsema_value, acquire);
      os_atomic_sub2o(dsema, dsema_value, 1, acquire)
      os_atomic_sub(&(dsema)->dsema_value, (1), acquire)
      _os_atomic_c11_op(&(dsema)->dsema_value, (1), acquire, sub, -)
      atomic_fetch_sub_explicit(&(dsema)->dsema_value, 1)
      等價於&(dsema)->dsema_value = &(dsema)->dsema_value -1
      複製代碼
    3. dispatch_semaphore_signal
      dispatch_semaphore_wait相反,主要是對併發量進行加加操做 image.png
  • 調度組

    調度組主要的做用就是監聽某些任務執行狀態,好比如今須要刷新UI可是須要兩個請求完成以後再刷新UI,此時就可使用調度組,能夠等待兩個異步任務完成以後收到完成通知後再刷新UI 核心函數:
    • dispatch_group_create 建立組
    • dispatch_group_async 進組任務
    • dispatch_group_notify 等待任務執行完成通知
    • dispatch_group_wait 暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成後,纔會往下繼續執行
    • dispatch_group_enter標誌着一個任務追加到 group,執行一次,至關於group 中未執行完畢任務數+1
    • dispatch_group_leave標誌着一個任務離開了 group,執行一次,至關於 group 中未執行完畢任務數-1。
    注意:dispatch_group_leavedispatch_group_enter須要成對出現
    使用示例:
    1. 正常狀況示例
      複製代碼
      打印狀況: image.png
    2. 使用dispatch_group_wait
      dispatch_group_t group = dispatch_group_create();
       dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           sleep(1);
           NSLog(@"任務1執行完成");
           dispatch_group_leave(group);
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           NSLog(@"任務2執行完成");
           dispatch_group_leave(group);
       });
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           sleep(3);
           NSLog(@"任務3執行完成");
       });
       dispatch_group_notify(group, dispatch_get_main_queue(), ^{
           NSLog(@"任務一、2執行完成");
       });
      
       NSLog(@"任務6執行完成(主線程)");
      複製代碼
      打印結果: image.png
    3. 特殊狀況1(將通知放在最前面)
      dispatch_group_t group = dispatch_group_create();
       dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
       dispatch_group_notify(group, dispatch_get_main_queue(), ^{
           NSLog(@"任務一、2執行完成");
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           sleep(1);
           NSLog(@"任務1執行完成");
           dispatch_group_leave(group);
       });
       dispatch_group_enter(group);
       dispatch_async(queue, ^{
           NSLog(@"任務2執行完成");
           dispatch_group_leave(group);
       });
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           sleep(3);
           NSLog(@"任務3執行完成");
       });    
      
       NSLog(@"任務6執行完成(主線程)");
      複製代碼
      打印結果: image.png 說明通知放在最前面只要執行dispatch_group_leave就會執行通知
    4. 特殊狀況2(多一個)dispatch_group_enter
      dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            sleep(1);
            NSLog(@"任務1執行完成");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"任務2執行完成");
            dispatch_group_leave(group);
        });
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"任務3執行完成");
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"任務一、2執行完成");
        });
        NSLog(@"任務6執行完成(主線程)");
      複製代碼
      打印狀況: image.png 發現dispatch_group_notify一直都不會執行
    5. 特殊狀況2(多一個)dispatch_group_leave image.png 直接會崩潰,因此說dispatch_group_leavedispatch_group_enter必定要成對出現
    源碼探索:
    • dispatch_group_create
      image.png image.png
    • dispatch_group_enter
      image.png image.png image.png
    • dispatch_group_leave
      image.png image.png
    • dispatch_group_async
      至關於幫開發者省去了dispatch_group_enterdispatch_group_leave兩個方法的調用 image.png image.png image.png 發現底層在建立以前調用了dispatch_group_enter方法 image.png 而後再任務執行完畢以後又調用了dispatch_group_leave
    • dispatch_group_notify
      image.png image.png
  • Dispatch Source

    在任⼀線程上調⽤它的的⼀個函數dispatch_source_merge_data後,會執⾏ Dispatch Source 事先定義好的句柄(能夠把句柄簡單理解爲⼀個 block )這個過程叫Custom event,⽤戶事件。是dispatch source⽀持處理的⼀種事件
    句柄是⼀種指向指針的指針 它指向的就是⼀個類或者結構,它和系統有很密切的關係 HINSTANCE(實例句柄),HBITMAP(位圖句柄),HDC(設備表述句柄),HICON (圖標句柄)等。這當中還有⼀個通⽤的句柄,就是HANDLE
    核心方法:
    1. dispatch_source_create建立源
    2. dispatch_source_set_event_handler設置源事件回調
    3. dispatch_source_merge_data源事件設置數據
    4. dispatch_source_get_data獲取源事件數據
    5. dispatch_resume繼續
    6. dispatch_suspend掛起
    使用示例:
    - (void)viewDidLoad {
         [super viewDidLoad];
    
    
         self.totalComplete = 0;
    
         self.queue = dispatch_queue_create("td.com", NULL);
    
         /* 
         DISPATCH_SOURCE_TYPE_DATA_ADD        自定義的事件,變量增長
         DISPATCH_SOURCE_TYPE_DATA_OR         自定義的事件,變量OR
         DISPATCH_SOURCE_TYPE_DATA_REPLACE    自定義的事件,變量Replace
         DISPATCH_SOURCE_TYPE_MACH_SEND       MACH端口發送    
         DISPATCH_SOURCE_TYPE_MACH_RECV       MACH端口接收 
         DISPATCH_SOURCE_TYPE_MEMORYPRESSURE  內存報警
         DISPATCH_SOURCE_TYPE_PROC            進程監聽,如進程的退出、建立一個或更多的子線程、進程收到UNIX信號
         DISPATCH_SOURCE_TYPE_READ            IO操做,如對文件的操做、socket操做的讀響應
         DISPATCH_SOURCE_TYPE_SIGNAL          接收到UNIX信號時響應
         DISPATCH_SOURCE_TYPE_TIMER           定時器
         DISPATCH_SOURCE_TYPE_VNODE           文件狀態監聽,文件被刪除、移動、重命名
         DISPATCH_SOURCE_TYPE_WRITE           IO操做,如對文件的操做、socket操做的寫響應
         DISPATCH_MACH_SEND_DEAD
         */
         self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
    
         dispatch_source_set_event_handler(self.source, ^{
    
             NSLog(@"%@",[NSThread currentThread]);
    
             NSUInteger value = dispatch_source_get_data(self.source);
             self.totalComplete += value;
             NSLog(@"進度: %.2f",self.totalComplete/100.0);
             self.progressView.progress = self.totalComplete/100.0;
         });
    
         self.isRunning = YES;
         dispatch_resume(self.source);
     }
     - (IBAction)didClickStartOrPauseAction:(id)sender {
    
         if (self.isRunning) {
             dispatch_suspend(self.source);
             dispatch_suspend(self.queue);
             NSLog(@"已經暫停");
             self.isRunning = NO;
             [sender setTitle:@"暫停中.." forState:UIControlStateNormal];
         }else{
             dispatch_resume(self.source);
             dispatch_resume(self.queue);
             NSLog(@"已經執行了");
             self.isRunning = YES;
             [sender setTitle:@"暫停中.." forState:UIControlStateNormal];
         }
     }
    
     - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
         NSLog(@"開始了");
    
         for (int i= 0; i<100; i++) {
             dispatch_async(self.queue, ^{
                 if (!self.isRunning) {
                     NSLog(@"已經暫停");
                     return;
                 }
                 sleep(1);
                 dispatch_source_merge_data(self.source, 1);
             });
         }
    
     }
    複製代碼
相關文章
相關標籤/搜索