GCD

GCD

GCD簡介

  • Grand Central Dispatch中樞調度器
  • 純C語言的,提供了非常強大的函數
  • 優勢
    • GCD是蘋果公司為多核的并行運算提出的解決方案
    • GCD會自動利用更多的CPU內核(比如雙核、四核),充分利用設備的多核
    • GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
    • github上面有源碼

任務和隊列

  • GCD中的核心概念
  • 任務:執行什么操作,需要用函數封裝起來(同步函數|異步函數)
  • 隊列:用來存放任務的
  • 使用步驟
    • 定制任務
      • 確定想要做的事情
    • 將任務添加到隊列中
      • GCD會自動將隊列中的任務取出,放到對應的線程中執行
      • 任務的取出遵循隊列的FIFO原則:先進先出,后進后出
  • 執行任務
    • 同步
      • dispatch_sync(dispatch_queue_t queue,dispatch_block_t block)
      • 只能在當前線程中執行任務,不具備開啟新線程的能力
    • 異步
      • dispatch_asyn
      • 可以在新的線程中執行任務,具備開啟新線程的能力
  • 隊列的類型
    • 并發隊列
      • 可以讓多個任務并發執行
      • 并發功能只有在異步函數下才有效
    • 串行隊列
      • 讓任務一個接著一個的執行
  • 容易混淆的術語
    • 同步異步:能不能開啟新的線程
      • 同步:只是在當前線程中執行任務,不具備開啟新線程的能力
      • 異步:可以在新的線程中執行任務,具備開啟新線程的能力
    • 并發|串行:任務的執行方式
      • 并發:允許多個任務并發執行
      • 串行:一個任務執行完畢之后,再執行下一個任務

GCD基本使用

  • 四種組合方式

  • 異步函數+并發隊列

    • 獲得隊列
      • dispatch_queue_t 結構體
      • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_CONCURRENT)創建
        • 第一個參數:C語言的字符串,給隊列起一個名字(推薦寫法:公司域名倒過來寫,后面跟上作用)名字的作用:是用來調試的
        • 第二個參數:隊列的類型,傳一個宏
          • DISPATCH_QUEUE_CONCURRENT
          • DISPATCH_QUEUE_SERIAL
    • 封裝任務并且把任務添加到隊列中
      • dispatch_async(queue,任務)
        • 第一個參數:隊列
        • 第二個參數:block可以封裝一個任務
      • 封裝任務
      • 把任務添加到隊列中
    • 會開多條線程,所有的任務是并發執行的
    • 注意:使用GCD的時候,具體開幾條線程,并不是由任務的數量來決定的,是由系統自動決定
      • 會看CPU的處理情況,線程是可以復用的
  • 異步函數+串行隊列

    • 獲得隊列
      • dispatch_queue_t 結構體
      • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIAL)創建
        • 第一個參數:C語言的字符串,給隊列起一個名字(推薦寫法:公司域名倒過來寫,后面跟上作用)
        • 第二個參數:隊列的類型,傳一個宏
          • DISPATCH_QUEUE_CONCURRENT
          • DISPATCH_QUEUE_SERIAL
    • 封裝任務并且把任務添加到隊列中
      • dispatch_async(queue,任務)
        • 第一個參數:隊列
        • 第二個參數:block可以封裝一個任務
      • 封裝任務
      • 把任務添加到隊列中
    • 會開啟一條子線程,所有的任務都是串行執行的,需要等前一個任務執行完,后面的任務才會執行
  • 同步函數+并行隊列

    • 獲得隊列
      • dispatch_queue_t 結構體
      • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_CONCURRENT)創建
        • 第一個參數:C語言的字符串,給隊列起一個名字(推薦寫法:公司域名倒過來寫,后面跟上作用)
        • 第二個參數:隊列的類型,傳一個宏
          • DISPATCH_QUEUE_CONCURRENT
          • DISPATCH_QUEUE_SERIAL
    • 封裝任務并且把任務添加到隊列中
      • dispatch_sync(queue,任務)
        • 第一個參數:隊列
        • 第二個參數:block可以封裝一個任務
      • 封裝任務
      • 把任務添加到隊列中
    • 不會開啟新的線程,所有的任務都在當前線程中串行執行
  • 同步函數+串行隊列

    • 獲得隊列
      • dispatch_queue_t 結構體
      • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIALRENT)創建
        • 第一個參數:C語言的字符串,給隊列起一個名字(推薦寫法:公司域名倒過來寫,后面跟上作用)
        • 第二個參數:隊列的類型,傳一個宏
          • DISPATCH_QUEUE_CONCURRENT
          • DISPATCH_QUEUE_SERIAL
    • 封裝任務并且把任務添加到隊列中
      • dispatch_sync(queue,任務)
        • 第一個參數:隊列
        • 第二個參數:block可以封裝一個任務
      • 封裝任務
      • 把任務添加到隊列中
    • 不會開啟新的線程,所有的任務都在當前線程中串行執行
  • 同步函數不管是串行還是并發都是串行執行任務的。

全局并發隊列

  • 并發隊列

    • 隊列中的任務可以同時執行
    • 并發隊列分為兩種
      • 自己創建的并發隊列dispatch_queue_create
      • 全局并發隊列(默認存在)
  • GCD默認已經提供了全局的并發隊列,供整個應用使用,可以無需手動創建

  • dispatch_get_global_queue函數獲得全局的并發隊列

    • 參數一:隊列的優先級dispatch_queue_priority
      • DISPATCH_QUEUE_PRIORITY_DEFAULT 默認優先級 == 0 ,不要傳其他的,會引發線程安全問題(反轉)
    • 參數二:暫時無用,用0即可,給未來使用的
  • 注意:線程的數量并不是由任務的數量決定的(是由系統決定的)

主隊列

  • 串行隊列
    • 必須一個接著一個的執行,要等當前任務執行完畢之后,才能執行后面的任務
    • 串行隊列有兩種
      • 自己創建的串行隊列dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIAL)
        • DISPATCH_QUEUE_SERIAL == NULL
      • 主隊列(和主線程相關)
        • 主隊列是GCD自帶的一種特殊的串行隊列
        • 放在主隊列中的任務,都會放到主線程中執行 | 調度的時候比較特殊!
        • dispatch_get_main_queue()獲得主隊列
  • 特點:
    • 凡是放在主隊列中的任務都必須要在主線程中執行
    • 本身是一個串行隊列,串行隊列的特點它都有
    • 如果主隊列中有任務需要執行,那么主隊列會安排主線程來執行當前隊列中的任務,但是在調度之前,會先檢查主線程的狀態,如果主線程空閑,如果主線程在忙,就暫停調度隊列中的任務,等到主線程空閑的時候再調度。
  • 主隊列+異步函數
    • 獲得主隊列
      • dispatch_get_main_queue()
    • 添加任務到隊列中
      • dispatch_async()
    • 不會開線程,所有的任務都在主線程中串行執行
  • 主隊列+同步函數
    • 獲得主隊列
      • dispatch_get_main_queue()
    • 添加任務到隊列中
      • dispatch_sync()
    • 任務并沒有執行,有一個死鎖
      • 主隊列里的任務必須要在主線程中執行
      • 主線程在執行同步函數
      • 主隊列工作模式:如果發現隊列中有任務,那么就安排主線程來執行,但是在安排之前, 會先檢查主線程的狀態(看主線程是否空閑),如果主線程在忙,那么就暫停調度,直到主線程空閑
    • 如果在子線程中調度就不會死鎖
      • performSelectorInBackground:@selector(主隊列+同步函數) withObject:
      • 不會發生死鎖了,為什么?
        • 主線程空閑,沒有事情做,主線程就執行了任務
  • 開發中,常用的是:異步函數+并發隊列|串行隊列,面試中會把代碼給你,問你打印的結果是什么

  • 問題:同步函數+主隊列會死鎖?異步函數+主隊列不會死鎖?

    • 同步異步:
      • 是否具有開線程的能力
      • 同步函數同步執行任務,異步函數異步執行任務
      • 同步執行:
        • 必須要等當前任務執行完,才能執行后面的任務,如果我沒有執行,那么我后面的也別想執行
      • 異步執行:
        • 我開始執行后,可以不用等我執行完,就執行后面的任務
      • 異步函數+主隊列不會發生死鎖
        • 獲得一個主隊列
        • 同步函數
          • 封裝一個任務
          • 把任務添加到隊列中去,檢查主線程狀態(較忙)死鎖
        • 異步函數
          • 隊列中有任務,要求主線程去執行
          • 異步函數異步執行,跳過任務,把任務存在隊列中
          • 代碼執行完畢后,主線程空閑了,就回來再執行隊列里的任務,就不會發生死鎖
  • 異步函數 +并發隊列 ps 同步函數+并發隊列
    • 1.start - end - 任務 - 異步
    • 2.start - 任務 - end - 同步
  • 問題:同步函數+串行隊列不會發生死鎖?

    • 串行隊列,在當前線程中執行,串行隊列有任務就會執行,不會檢查主線程的狀態, 不管線程是否在忙,都會強行讓線程來執行任務
  • 總結

    • 異步函數+不是主隊列就一定會開線程
    • 同步+并發:不會線程,串行執行任務
    • 同步+串行:不會開線程,串行執行任務
    • 同步+主隊列:不會開線程,死鎖
    • 異步+并發:開多條線程,并發執行任務
    • 異步+串行:開線程,串行執行任務
    • 異步+主隊列:沒有開啟新線程,串行執行任務

線程間通信

  • 開子線程下載圖片
    • 異步+非主隊列
    • 獲得全局并發隊列
      • dispatch_get_global_queue(0,0)
    • 使用異步函數+并發隊列
      • dispatch_async(queue,block)
        • 確定URL
        • 把圖片的二進制數據下載到本地
          • dataWithContentsOfURL:
        • 轉化格式
          • imageWithData:
        • 顯示圖片
          • 回到主線程刷新UI
          • GCD可以嵌套
          • dispatch_async(dispatch_get_main_queue(),^{
            self.imageV.image = image;
            })
    • 配置info.plist文件
  • 問:回到主線程,是異步函數+主隊列,在這里可以用同步函數+主隊列嗎?
    • 可以,當前執行的是子線程,不會發生死鎖

常用函數

  • 開發中比較常用的GCD函數
延遲執行
  • 一段時間之后再執行任務
  • 三種方法可以實現延遲執行
    • 方法一:performSelector:withObject:afterDelay:
    • 方法二:定時器NSTimer scheduledTimeWithTimeInterval:target:selector:userInfo:repeats:
    • 方法三:GCD dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3.0*NSEC_PER_SEC)),dispatch_get_main_queue),^{}
      • 默認是主隊列 ,可以改為全局并發隊列
      • 隊列決定代碼塊在哪個線程中調用
      • 主隊列 - 主線程
      • 并發隊列 - 子線程
      • 好處:可以控制在哪個線程里面執行任務
  • 延遲執行內部實現原理?
    • A:先把任務提交到隊列,然后三秒之后再執行該任務(錯誤)
    • B:先等三秒的時間,然后再把任務提交到隊列
    • 正確答案:B
    • 延遲執行就是延遲提交
一次性代碼
  • 只會執行一次,一輩子只會執行一次
  • dispatch_once(&onceToken,^{});
  • 特點:
    • 整個程序運行過程中,只會執行一次
    • 它本身是線程安全的,不用考慮線程安全的問題
  • 怎么做到永遠只會執行一次呢?
    • 通過靜態變量的值來判斷的
      • static dispatch_once_t onceToken;
    • 判斷onceToken值是否等于0,不等于零就不執行了
快速迭代
  • 迭代 - 遞歸 - 遍歷 - 博客ios開發中實用技巧

  • 遍歷

    • for循環,開多條線程就可以提高效率
    • GCD的快速迭代dispatch_apply(10,并發隊列,^(size_t index){})
      • 參數一:遍歷多少次,size_t 類似NSInteger類型
      • 參數二:對列,看到隊列就要想,block塊在哪個線程中調用
      • 參數三:^(size_t ){}類似于for循環里面的i
  • for循環與GCD的快速迭代區別

    • 快速迭代沒有順序,開始迭代的時候是按照順序開始的,誰先結束是不知道的,所以打印出的結果是沒有順序的,是并發執行的
    • GCD快速迭代:會開啟多條子線程和主線程一起并發的執行任務
    • 注意:隊列怎么傳?不能傳主隊列!不傳串行隊列,不會死鎖,不會開線程,執行方式和for循環一樣,串行執行的!傳并發隊列!!!
    • 用快速迭代就用并發隊列!!!!
  • 快速迭代的應用

    • 剪切文件
      • 獲得路徑下面的子路徑(打印的是文件的名稱,剪切的時候需要的是文件的全路徑)
        文件管理者NSFileManager defaultManager]subpathsAtPath:
      • 遍歷數組(快速迭代)
dispatch_get_global_queue(0,0)
dispatch_apply(subpaths.count,queue,^(size_t){
                獲得文件的名稱
                獲得要剪切文件的全路徑(stringByAppendingPathComponent:該方法在拼接的時候會自動添加一個/)
                拼接文件的目標路徑(toFullPath)
                剪切文件
                [NSFileManager defaultManager] moveItemAtpath:toPath:error:(第一個參數:要剪切的文件在哪里?第二參數:要剪切到哪里去,全路徑;第三個參數:)
                }) 
柵欄函數
  • 獲得隊列:并發
    • dispatch_queue_create("",DISPATCH_QUEUE_CONCURRENT)
    • 為什么不用全局并發隊列?
      • 柵欄函數不能使用全局并發隊列
      • 如果使用全局并發隊列,柵欄函數就不具備攔截功能,和普通的異步函數沒有任何區別
      • 使用柵欄函數,必須要自己創建隊列
  • 異步函數
    • dispatch_async(queue,^{})
  • 需求:真實開發中,任務和任務之間有依賴關系,有先后順序關系,要求執行123三個任務后,執行打印操作,再執行后面的456三個任務
  • 柵欄函數
    • dispatch_barrier_async()一般用異步函數
    • 在柵欄函數內部執行打印操作
    • 作用:
      • 等之前的所有任務都執行完畢之后,執行柵欄函數中的任務,等我的任務執行完畢之后,在執行后面的任務
      • 前面的和后面的任務都是并發執行的
GCD隊列組(掌握)
  • 和柵欄函數的功能很類似

  • 1.獲得隊列

    • 全局并發隊列dispatch_get_global_queue()
  • 異步函數dispatch_async(queue,^{})

  • 需求,在最后攔住任務,當所有的任務執行完畢之后做一些事情!

  • 隊列組(調度組)

  • 2.創建隊列組dispatch_group_create()

    • 監聽隊列里面所有任務的執行情況
  • 3.異步函數dispatch_group_async()

    • 第一個參數:隊列組
    • 第二個參數:隊列
    • 通過隊列組建立起隊列組和隊列以及任務之間的關系
  • 4.攔截dispatch_group_notify(group,queue,^{打印操作})當隊列組中所有的任務都執行完畢之后,會調用該方法

  • 隊列組是如何知道任務什么時候結束的呢?

    • 以前是把dispatch_group_async(group,queue,^{})拆分成三個方法
      • 1.創建隊列組
      • 2.獲得對列
      • 3.該方法后面的異步任務會被隊列組監聽dispatch_group_enter(group)
      • 4.使用異步函數封裝任務
      • dispatch_async(queue,^{任務1,在里面監聽,enter和leave配對使用dispatch_group_leave(group)})
      • 5.攔截通知,隊列決定block塊在哪個線程中執行 dispatch_group_notify(group,queue,^{})
  • dispatch_group_async()和dispatch_async()的區別

    • 相同點:
      • 封裝任務
      • 把任務添加到隊列
    • 不同點
      • 隊列組能夠監聽該任務的執行情況(開始|結束)
  • 隊列組的應用

    • 應用場景:下載圖片1,圖片2,把兩張圖片合成為一張圖片
      • 耗時操作,耗時操作放到子線程中執行
      • 要開2~3條子線程
      • 獲得一個并發隊列dispatch_get_global_queue(0,0)
      • 合成圖片操作,有一個隱藏的依賴關系
        • 隊列組
        • 柵欄函數
      • 創建隊列組dispatch_group_creat()
      • 異步函數dispatch_group_async(group,queue^{
        URL:URLWithString
        把圖片的二進制數據下載到本地dataWithContentOfURL:
        轉換格式imageWithData
        定義圖片的屬性,強引用轉換好的圖片
        })
      • 合成圖片
        • dispatch_group_notify(group,queue,^{
          開啟圖形上下文UIGraphicsBeginImageContext()
          畫圖1和2 drawInRect:
          根據上下文得到圖片 UIGraphicsGetImageFromCurrentImageContext()
          關閉上下文UIGraphicsEndImageContext()
          顯示圖片(線程間通信)
          dispatch_async(dispatch_get_main_queue(),^{
          self.imageV.image = image;
          })
          })

補充

  • GCD的其他用法

    • 使用函數的方法封裝任務dispatch_async_f()
      • 第一個參數:隊列
      • 第二個參數:函數要接收的參數NULL
      • 第三個參數:dispatch_function_t
        • void(*dispatch_function_t)(void *)
        • (*dispatch_function_t) -- run把第一個小括號替換成函數名稱
        • 第三個參數直接傳run就可以了
  • 全局并發隊列與自己創建的并發隊列的區別

    • 全局并發隊列在整個應用程序中本身是默認存在的,并且對應有高優先級、默認優先級、低優先級和后臺優先級一共四個并發隊列,我們只選擇其中的一個直接拿來用,而creat函數是實打實的從頭開始去創建一個隊列
    • 在ios6.0之前,在GCD中凡是使用了create和retain的函數在最后都需要做一次release操作。而主隊列和全局并發隊列不需要我們手動release.當然了,在ios6.0之后GCD已經被納入到了ARC的內存管理范疇中,即便是使用retain或者create函數創建的對象也不再需要開發人員手動釋放,我們像對待普通OC對象一樣對待GCD就OK
    • 在使用柵欄函數的時候,蘋果官方明確規定,柵欄函數只有在和使用create函數自己創建的并發隊列一起使用的時候才有效果(沒有給出具體原因)
    • 其他區別涉及到XNU內核的系統級線程編程,不一一列舉
    • 參考
      • GCDAPI
      • Libdispatch版本源碼
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容