iOS多線程編程——淺談GCD

來源:互聯網
上載者:User

標籤:

GCD對於iOS開發人員來說肯定不陌生,他和NSThread,NSOperation一起作為iOS開發中主要的三種多線程實現方法,而GCD是最最底層的,所以對於作為一個iOSer,GCD是必須掌握的。

我通過對於以下兩篇文章的閱讀,基本上掌握了GCD的基本使用方法。所以首先感謝兩位作者。

  • GCD 深入理解:第一部分
  • iOS多線程開發——GCD的使用與多線程開發淺析(二)
一、基本概念

對於新手來說,最常見同時最容易搞混的的莫過於GCD中的一些基本概念了。

  1. 並行與並發(Parallelism && Concurrency)

    • 並行:顧名思義就是同時行動,兩個任務在兩個線程(Thread)上進行處理,彼此互不干涉。而究其根本是因為多核進行處理,從而更快的解決任務。(可以類比於兩條水管中同時給遊泳池放水)
    • 並發:顧名思義,同時發生。對於有的效能較差的機子,比如說那些只有單核的裝置,為了讓使用者感覺能夠同時處理多個任務,他就需要通過不斷的切換正在處理的線程,從而實現一種“偽並行”,這樣就防止使用者因為一個任務進行太久而無法進行下一個任務。(可以類比於我用兩個口給遊泳池放水,不過因為只有一根水管,而放水的方向有2個方面,所以需要不斷的切換,從而實現兩邊同時達到需要的水位)
    • 區分如:
  2. 串列與並發(Serial && Concurrent)

    • 串列:由於所有任務在一個線程被執行的時候只有一個任務會被執行,所以每個任務都要依賴於先來先處理(FIFO)原則,處理所有得到的內容。
    • 並行:當接收到任務的時候,並行狀態下,他會開啟多個線程,將每個任務都分配到各個線程中,從而使得每個任務都能夠在同一時間被執行。這樣有效減少了所消耗的時間。
    • 區分圖如下:

  3. 隊列:就像前面所說,當你告訴電腦你需要執行哪些事情的時候,電腦就會把你告訴他的一件件事情放到自己的隊伍中,很明顯,先告訴他的事情放在前面,後告訴他的事情放在後面,即先進先處理(FIFO)原則處理所有事物,就像排隊一樣。

    • 在GCD中主要的隊列有2種,就是前面提到的:
      • 串列隊列
      • 並發隊列
    • 蘋果公司提供給我們的已存在的隊列有5種(不包括後期我們自己添加的內容)
      • 主隊列(main queue):預設情況下所有事情都是在主隊列下進行處理,只要有編碼經驗的人來說,主隊列很明顯是(串列隊列)。
      • 全域調度隊列(Global Dispatch Queues):預設情況下全域隊列是系統提供的並發隊列的統稱,根據隊列的優先程度不同分為以下幾個(優先度從低到高):
        • background
        • low
        • default
        • high
  4. 同步與非同步(synchronization && asynchronous)
    -同步:同步指的是在原來的內容執行完成之後,再執行你下面所需要的任務。
    -非同步:非同步指的是在和原來的內容執行的同時,在另一個地方同時處理新的任務。
二、基本使用

既然學了,那肯定要使用它。那麼我們先類比一個環境:
這個程式裡面只有主隊列,然後你想從網上下載一個圖片,放到你自己的手機裡面,然後使用者就可以對手機裡面的這張圖片進行各種各樣的處理。
但是問題來了:我們都知道下載需要時間,雖然現在已經是4G時代,下載速度很快,但是如果這個圖片很大,那麼他就需要很長的時間來進行等待。而在這段時間內,使用者什麼事情都不能幹,只能默默的乾等著。

這很明顯是一個很差的使用者體驗

那麼既然我們已經知道問題所在,就需要想辦法解決它:

  1. 我們需要在使用者等待的時候添加一個動畫效果,告訴他我們正在下載(這個和今天主題無關,我們就不繼續深入瞭解)
  2. 將下載任務放到後台(如果仍舊放在前台的話,就算有動畫,動畫效果也不會動)

那麼這個時候就需要非同步隊列的存在了

就像前面的基本概念中所提到的,執行分為同步執行和非同步執行,任務隊列分為串列隊列和並發隊列。所以他們彼此一一結合,總共有4種情況存在,主要的彼此結合的狀態圖如下所示:

單隊列串列隊列,同步執行

代碼如下:

func serialDispatchQueueWithSync() {    let queue = dispatch_queue_create("serialDispatchQueue", DISPATCH_QUEUE_SERIAL)    print("0")    dispatch_sync(queue) {        print("1")    }    dispatch_sync(queue) {        print("2")    }    dispatch_sync(queue) {        print("3")    }    dispatch_sync(queue) {        print("4")    }    dispatch_sync(queue) {        print("5")    }    print("6")}

運行結果如下:

0123456
串列隊列,非同步執行

代碼如下:

func serialDispatchQueueWithASync() {    let queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL)    print("0")    dispatch_async(queue) {        print("1")    }    dispatch_async(queue) {        print("2")    }    dispatch_async(queue) {        print("3")    }    dispatch_async(queue) {        print("4")    }    dispatch_async(queue) {        print("5")    }    dispatch_async(queue) {        print("6")    }    print("7")}

運行結果如下:

07123456

解釋:因為queue是非同步執行的,即調用dispatch_async函數,所以輸出1 2 3 4 5 6的時候是在另一個線程中的,所以和主線程中的輸出1 7 無關。

並發隊列,同步執行

代碼如下:

func concurrentDispatchsQueueWithSync() {    let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)    print("0")    dispatch_sync(queue) {        print("1")        print("12")        print("13")    }    dispatch_sync(queue) {        print("2")        print("22")        print("23")    }    dispatch_sync(queue) {        print("3")        print("32")        print("33")    }    dispatch_sync(queue) {        print("4")        print("42")        print("43")    }    print("5")}

運行結果如下:

0112132222333233442435
並發隊列,非同步執行

代碼如下:

func concurrentDispatchsQueueWithASync() {    let queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT)    print("0")    dispatch_async(queue) {        print("1")        print("12")        print("13")    }    dispatch_async(queue) {        print("2")        print("22")        print("23")    }    dispatch_async(queue) {        print("3")        print("32")        print("33")    }    dispatch_async(queue) {        print("4")        print("42")        print("43")    }    print("5")}

運行結果如下:

071212322134322354233524353
其他常見用法:掛起與恢複
  • 線程掛起:dispatch_suspnd()
  • 線程恢複:dispatch_resume()
訊號量

由於有的時候(比如說在建立添加數組中的對象的時候),因為不同線程操縱的是同一個對象,所以很容易發生報錯,這個時候需要通過訊號量來對對應的內容進行控制,當訊號量為0的時候,進入等待狀態,不能執行下面的內容;當訊號量為1的時候,可以執行,同時訊號量減一,等到執行完畢,訊號量加一。
- 等待執行dispatch_semaphore_wait()
- 訊號量加一 dispatch_semahore_signal

只執行一次

有的時候,有的東西的建立只能被建立一次(即單例),這個時候就需要用到dispatch_once()
代碼如下:

var token: dispatch_once_t = 0func test() {    dispatch_once(&token) {        println("This is printed only on the first call to test()")    }    println("This is printed for each call to test()")}
關於讀寫問題的解決方案(買票問題)

由於日常編碼的過程中經常會遇到,當你在調用這個這個變數的時候。另一個線程也在調用這個變數(只在並發過程中),如果兩個線程都是在讀取資料,那並沒有什麼問題,但是如果其中一個在寫入,或者兩個都在寫入,那麼就會出現很大問題,所以蘋果官方在GCD中也給我們準備了一個方法,讓我們解決這個問題,那就是dispatch_barrier_async,這個方法讓對的內容在並發過程中加入,從而方便組織內容的修改,從而使得對應的內容只能在當前線程中被進行修改。

常見問題:死結:
let queue = dispatch_get_main_queue()dispatch_async(queue) {     dispatch_sync(queue, {         print("1")    })}

產生原理:因為dispatch_sync()會等到本身結束之後才會在主線程繼續執行接下去的代碼,但是dispatch_sync()這個方法調用的就是主線程,所以午飯等到主線程結束,所以就無法返回,就會卡在這裡。

解釋:由於是並發隊列,所以他會建立多個線程,從而保證每個線程的任務都能夠儘快完成,所以順序有一定的出入。

最後通過兩張動態圖來最後總結單隊列:dispatch_sync
- (void)viewDidLoad{  [super viewDidLoad];  dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{    NSLog(@"First Log");  });  NSLog(@"Second Log");}

  1. 主隊列按照預定的順序下來
  2. viewDidLoad在主線程進行執行。
  3. 直到執行到dispatch_sync
  4. 調用dispatch_sync代碼,將block添加到全域隊列中,主隊列掛起。
  5. 全域隊列先完成之前存放在全域隊列中的內容。
  6. 完成之前的任務後,執行dispatch_sync的block中的內容。
  7. 完成block中的任務,主隊列上的任務得以恢複
  8. 主隊列繼續執行其他任務。
dispatch_async
- (void)viewDidLoad{  [super viewDidLoad];  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{    NSLog(@"First Log");  });  NSLog(@"Second Log");}![dispatch_sync](https://camo.githubusercontent.com/2c7cbaf76001a56622e14cf48a8d914d4b5c9df4/687474703a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f64697370617463685f6173796e635f696e5f616374696f6e2e676966)
  1. 主隊列一路按順序執行任務——接著是一個執行個體化UIViewController 的任務,其中包含了viewDidLoad
  2. viewDidLoad在主線程執行。
  3. 主線程目前在viewDidLoad內,正要到達dispatch_async
  4. dispatch_async Block被添加到一個全域隊列中,將在稍後執行。
  5. viewDidLoad在添加dispatch_async到全域隊列後繼續進行,主線程把注意力轉向剩下的任務。同時,全域隊列並發地處理它未完成地任務。記住Block在全域隊列中將按照(FIFO)順序出列,但可以並發執行。
  6. 添加到dispatch_async的代碼塊開始執行。
  7. dispatch_async Block完成,兩個NSLog語句將它們的輸出放在控制台上。
調度組

既然能夠處理一個個的任務,那麼我們就繼續類比一個環境,當我們需要在網上下載內容的時候(這些內容需要彼此聯絡在一起才能正常使用),這個時候,上面的單隊列就不夠了(或者說如果使用單隊列產生的效果不是時間太長,就是檔案的完整性不夠好)

這個時候我們就需要引入多隊列,當多個內容都處理完成之後,讓系統告訴我們,我們已經完成了以上的下載。可以繼續做下一步事情了。

任務開始

在GCD中,我們可以通過dispatch_group_enter來通知當前任務的開始,而與之相對應的,我們必須在任務完成後,手動通知調度組任務結束(dispatch_group_leave)這樣才能讓調度組知道我們這個任務已經結束。

代碼如下(由於demo中有一個Photo類,所以此處貼上OC代碼):

dispatch_group_enter(downloadGroup); // 3Photo *photo = [[Photo alloc] initwithURL:url withCompletionBlock:^(UIImage *image, NSError *_error) {    if (_error) {        error = _error;    }dispatch_group_leave(downloadGroup); // 4}];[[PhotoManager sharedManager] addPhoto:photo];
任務提醒

當我們所有的任務都手動通知後,那麼就需要條用提醒來告訴他(我已經完成了所有內容,接下去需要試試哪裡能夠執行了),而提醒方式在GCD中有兩種:

  • dispatch_group_wait() + dispatch_async()
  • dispatch_group_notify
其他常見用法dispatch_apply()

有的時候需要調用for迴圈來反覆執行,但是當需要執行的代碼量偏大的時候,for的效率比較低,這個時候需要用dispatch_apply()來執行,這樣節省效率,不過當需要執行的代碼量比較小的時候,dispatch_apply()的效率就比較差了。

這個方法的效果和dispatch_sync一樣,所以要注意死結(後面會提到)

iOS多線程編程——淺談GCD

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.