iOS多線程(GCD)

一. 了解GCD是什么

GCD的全稱是Grand Central Dispath, "強大的中樞調度器".

GCD的優勢
GCD是蘋果公司為多核的并行運算提出的解決方案
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼

二. 關于GCD和NSOperation的描述,以及實際生產中如何選擇

其實我們在通過NSOperation和GCD進行開發過程中,會發現兩者執行的方式有許多相似之處,NSOperation和GCD參照對比,NSOperationQueue和dispatch_queue參照對比,但是兩者之間還是有許多差別的,具體區別:

  1. GCD的核心是C語言寫的系統服務,執行和操作簡單高效,因此NSOperation底層也通過GCD實現,換個說法就是NSOperation是對GCD更高層次的抽象,這是他們之間最本質的區別.因此如果希望自定義任務,建議使用NSOperation;
    2.依賴關系,NSOperation可以設置兩個NSOperation之間的依賴,第二個任務依賴于第一個任務完成執行,GCD無法設置依賴關系,不過可以通過dispatch_barrier_async來實現這種效果;
    3.KVO(鍵值對觀察),NSOperation和容易判斷Operation當前的狀態(是否執行,是否取消),對此GCD無法通過KVO進行判斷;
    4.優先級,NSOperation可以設置自身的優先級,但是優先級高的不一定先執行,GCD只能設置隊列的優先級,無法在執行的block設置優先級;
    5.繼承,NSOperation是一個抽象類實際開發中常用的兩個類是NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執行任務可以自由組裝,沒有繼承那么高的代碼復用度;
    6.效率,直接使用GCD效率確實會更高效,NSOperation會多一點開銷,但是通過NSOperation可以獲得依賴,優先級,繼承,鍵值對觀察這些優勢,相對于多的那么一點開銷確實很劃算,魚和熊掌不可得兼,取舍在于開發者自己;

實際開發中,我們使用更多的其實還是GCD,畢竟GCD有更高的并發和執行能力.

GCD的簡單實用

  • 1.異步 并發 隊列
// 異步 并發 隊列
- (void)asyncConcurrent {
    // 創建一個 并發線程(這種方式是手動創建出來一個并發隊列對象,不是系統原本就存在的)
//    dispatch_queue_t queue = dispatch_queue_create("hie.com", DISPATCH_QUEUE_CONCURRENT);
    
    /*
     第一個參數:優先級
     第二個參數:給未來使用,一般設置為0就行
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);// 這種方式同樣可以獲取到一個并發隊列,只不過這種方式獲取的并行隊列原本就是存在的,不是我們創建出來的
    
    dispatch_async(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
}

執行結果如下圖:

Paste_Image.png
  • 2.異步 串行 隊列
- (void)asyncSerial {
    // 創建串行隊列,隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL
    dispatch_queue_t queue = dispatch_queue_create("hie.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
}

執行結果如下圖:


Paste_Image.png
  • 3.同步 并發 隊列
// 同步 并發 隊列
- (void)syncConcurrent {
    
    dispatch_queue_t queue = dispatch_queue_create("hie.com", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
}

執行結果如下圖:

Paste_Image.png
  • 4.同步 串行 隊列
// 同步 串行 隊列
- (void)syncSerial {
    
    dispatch_queue_t queue = dispatch_queue_create("hie.com", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
}

執行結果如下圖:

Paste_Image.png

下邊總結一下:
創建一個并發隊列通常我menu會實用一下2中方式:

  • 1.手動創建一個并發隊列對象
dispatch_queue_t queue = dispatch_queue_create("hie.com", DISPATCH_QUEUE_CONCURRENT);
  • 2.獲取一個系統的并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

注意

/*
 能不成開線程,只看是 同步 還是 異步函數,是異步函數,就能開線程,同步線程不能開線程,跟并發隊列還是串行隊列無關,串行隊列和并發隊列的區別僅僅是任務執行的書序,串行隊列中,任務順序執行,并發隊列中任務并發執行(但是如果是多個同步函數,無論在串行還是并行隊列中,都是串行執行的)。
 還有一個要注意的是:我們開了很多個異步函數,并不代表這我們就開啟了同函數個數相同數量的線程個數,至于具體開啟了多少條線程,其實是系統說了算的,跟我我們無關。
 */

主隊列(一個特殊的隊列)

主隊列有一個特性,所有添加到主隊列的函數,都會 添加到主線程中來執行。

  • 1.異步 主線程
// 異步 主隊列   所有任務都在主線程中執行,不會開線程
- (void)asyncMain {
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
}

執行結果如下圖:

Paste_Image.png
  • 2.同步 主隊列
- (void)syncMain {
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"started");
    
    dispatch_sync(queue, ^{
        NSLog(@"download1--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2--%@", [NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3--%@", [NSThread currentThread]);
    });
    
    NSLog(@"end");
    
}

執行結果如下圖:

Paste_Image.png

注意:
這里我們看到,添加到隊列中的任務并沒有執行,原因是產生了死鎖
產生死鎖的原因:主隊列的特性是添加到主隊列的任務,都會添加到主線程執行。當我們把任務添加到主隊列以后,主隊列會給任務安排線程執行,就把他放到了主線程中,然后檢查主線程中時候有任務在執行,有任務在執行的話,會一直等到主線程空閑為止。在這時候檢查主線程,他會發現主線程中有任務在執行,所以會一直等下去,這樣就產生了死鎖。

  • 3.這樣的話我們在子線程中執行以下上邊的方法:
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

執行結果如下圖:

Paste_Image.png

我們發現并沒有產生死鎖,原因是我們在子線程中執行的方法,當檢查主線程中是否有任務執行的時候,會看到沒有任務執行,所以我們添加的任務就會被執行,所以,這樣既不會產生死鎖。
總結:通過上邊的實踐,我們發現,不要在主隊列中執行同步任務,這樣會產生死鎖。

通過上邊的各種寫法,我們總結出來一張如下圖的圖標:

Paste_Image.png

由于真正使用過程中,我們基本只關心如何開啟一個新的線程并執行任務,所以我們只需要關注用紅色線條圈中的部分,也就是本文中,最上邊的2中情況,異步并發隊列和異步串行隊列。最后要謹記一點,以防在實際使用中踩雷:通過同步函數sync創建的任務,添加到任何串行隊列(main和serial),都會阻塞當前串行隊列,產生死鎖。

線程間的通信

GCD中線程間的通信只需要通過嵌套就可以實現,我們拿最常用的在子線程下載圖片,回到主線程刷新UI(任何關于UI的刷新操作都必須在主線程中執行)的例子的介紹一下具體實現方式。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURL *url = [NSURL URLWithString:@"http://img5.imgtn.bdimg.com/it/u=1597522748,303041811&fm=23&gp=0.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        
        // 回到主線程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });
}

其他常用的GCD方法

延遲執行:dispatch_after
一次執行代碼:dispatch_once 等。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容