iOS GCD入門和GCD對CPU多核的使用

前言

好開心

明天要給師弟開分享會,分享GCD。 好方,只理解一些皮毛拿什麼去裝。準備的時候順便把過程記錄下來。安全

目錄

  • 概念
  • 簡單瞭解用法
  • 開發中經常使用的作法
  • GCD其餘的一些API
  • GCD會遇到的問題

和GCD有關的基本概念

術語 含義
進程 打開一個App就是開啓一個進程。
線程 獨立執行的代碼段,一個線程同時間只能執行一個任務,反之多線程併發就能夠在同一時間執行多個任務。在iOS系統中,一個進程包含一個主線程,它的主要任務是處理UI。其餘線程稱爲子線程。
同步 A執行完再執行B。
異步 A和B能夠同時執行。
任務 能夠理解爲某一堆要執行的代碼。分爲同步執行任務和異步執行任務。用block定義。
同步執行任務 按進入順序執行的任務
異步執行任務 無論進入順序,能夠一塊兒執行
隊列 存聽任務的結構。分爲串行隊列和並行隊列。遵循先進先出。
隊列組 將多線程進行分組,最大的好處是可獲知全部線程的完成狀況。
串行隊列 線程執行只能依次逐一前後有序的執行。
並行隊列 指兩個或多個事件在同一時刻發生。多核CUP同時開啓多條線程供多個任務同時執行,互不干擾。
併發 指兩個或多個事件在同一時間間隔內發生。能夠在某條線程和其餘線程之間反覆屢次進行上下文切換,看上去就好像一個CPU可以而且執行多個線程同樣。實際上是僞異步。
  • 一個有助於判斷執行完成時間的理論。 開線程須要消耗內存,因此要消耗時間

回頭看以爲有必要在這簡單說明多線程 多核 併發並行的區別子線程和主線程的聯繫bash

最近玩了個遊戲叫《Inside》,戴着頭盔就能操縱機器人,感受不管是玩法仍是遊戲劇情都超適合類比線程。 用這個舉個例子。 假如你是國王,拿到了一張藏寶圖,但這個寶藏要到每個地點才能得知下一個地點的信息(電路中內存地址)。因而你就操縱機器人A去找,找到後帶回來。機器人A的路線就是一條線程。 當機器人A還在路程上,你又獲得一張藏寶圖。你這時候派機器人B去找,找到帶回來。這時候機器人B的路線就是另外一條線程。 以上就是多線程。 這時候,只要你週期足夠短,輪流戴頭盔a和頭盔b,,看上去就像你同時在操縱機器人A和機器人B。這就叫作併發!裝出來的。 某一天,你的頭快搖傻了。因而乎你長出了第二個頭。(對應着雙核CPU),這時候就是名副其實地同時操縱。這就叫並行,必需要多頭怪才擁有這技能。 但若是又操縱第三個機器人,這時候只能再來回戴了,又要併發了。 A找到並回到了城堡把結果帶回給你,你才發現你也是個機器人(主線程)。其餘機器人帶回寶藏後就能夠拜拜了,但就算還有沒有寶藏在路上,你都不能拜拜,必須保持呼吸(runloop)。 這就是子線程和主線程的聯繫。 子線程的任務所有完成後,最終會回到主線程。主線程中運行着runloop網絡

簡單瞭解用法

就是把任務加到隊列中 隊列能夠本身新建。 系統也有 全局併發隊列主隊列多線程

#pragma mark - 建立隊列
//  建立隊列
//  第一個參數 隊列名稱
//  第二個參數的做用:串行(DISPATCH_QUEUE_SERIAL)、並行(DISPATCH_QUEUE_CONCURRENT)。
  dispatch_queue_t queue = dispatch_queue_create("net.Hsusue.testQueue", DISPATCH_QUEUE_CONCURRENT);
* 經常使用的系統併發隊列——全局併發隊列
//程序默認的隊列級別,通常不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 獲取主隊列有特別函數(是個串行隊列)
//  dispatch_queue_t queue = dispatch_get_main_queue();
#pragma mark - 建立任務加到隊列中
//  同步執行任務建立方法
  dispatch_sync(queue, ^{
  // 這裏放同步執行任務代碼
  });
// 異步執行任務建立方法
  dispatch_async(queue, ^{
  // 這裏放異步執行任務代碼
  });
複製代碼

我的認爲易迷惑的點

  • 太多的組合方式 有兩種任務執行方式,兩種隊列+特殊的主隊列,就能夠組成六種組合。 有兩張圖總結得特別好,記住這兩張圖,分析的時候用獲得。 而後爲了更好理解,本身也花了點時間弄了動圖。

image.png

image.png

仍是不能忘了《Inside》的例子。併發

  • 兩種待辦任務表(對應隊列) 一種是多個機器人對多個寶藏,先入先出發。(對應並行隊列) 另外一種是一個機器人對有順序找的多個寶藏。(對應串行隊列) 特殊的 強行本身去作的任務表。 (對應主隊列)app

  • 你有兩類事情(對應任務) 一類是吃喝拉撒,一有須要就本身立刻去作,總不能懶到讓機器人幫忙吧。(對應着同步執行任務) 另外一類是尋寶,要機器人去作,出發前要點時間給機器人充電。(對應着異步執行任務)異步

代碼中, 輸出@"1"對應着吃喝拉撒async

  1. 異步 + 並行隊列 (多個機器人找多個寶藏)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self asyncConcurrent];
    NSLog(@"1");
}

//異步執行 + 並行隊列
- (void)asyncConcurrent{
    //建立一個並行隊列
    dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"---start---");
    
    //使用異步函數封裝三個任務
    dispatch_async(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    
    NSLog(@"---end---");
}
複製代碼

多個機器人找多個寶藏

異步 + 並行隊列(代碼一開始是任務123)
多個機器人找多個寶藏,完成程度和你的吃喝拉撒沒必然前後順序。

  1. 異步 + 串行隊列 (一個機器人找有序寶藏)
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
    [self asyncSerial];
    NSLog(@"1---%@", [NSThread currentThread]);
}
//異步 + 串行隊列
- (void)asyncSerial{
    //建立一個串行隊列
    dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"---start---");
    //使用異步函數封裝三個任務
    dispatch_async(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    NSLog(@"---end---");
}
複製代碼

一個機器人找有序寶藏

異步 + 串行隊列
一個機器人等有序寶藏圖拼接好後,就出發了。和你吃喝拉撒沒前後順序。

  1. 同步 + 並行隊列 (本身吃喝拉撒 放到 多個機器人對多個寶藏,準備好後機器人一塊兒出發)
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
    [self syncConcurrent];
    NSLog(@"1---%@", [NSThread currentThread]);
}
//同步 + 並行隊列
- (void)syncConcurrent{
    //建立一個並行隊列
    dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"---start---");
    //使用同步函數封裝三個任務
    dispatch_sync(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    NSLog(@"---end---");
}
複製代碼

本身吃喝拉撒 放到 多個機器人對多個寶藏,準備好後機器人一塊兒出發

同步 + 並行隊列
一要吃喝拉撒就本身立刻去作。因此不等@「end」輸出就先作完了。最後再@「1」。有着必然前後順序。

  1. 同步+ 串行隊列 (本身吃喝拉撒 放到 一個機器人對有順序找的多個寶藏)
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
//    [self syncConcurrent]
    [self syncSerial];
    NSLog(@"1---%@", [NSThread currentThread]);
}

//同步 + 串行隊列
- (void)syncSerial{
    //建立一個串行隊列
    dispatch_queue_t queue = dispatch_queue_create("標識符", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"---start---");
    //使用異步函數封裝三個任務
    dispatch_sync(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    NSLog(@"---end---");
}
複製代碼

同步 + 串行隊列
和上面同樣 偷懶沒改字
此次更過度了,試圖讓一個機器人幫本身拉三次尿。。。但機器人作不到。

一要吃喝拉撒就本身立刻去作。因此不等@「end」輸出就先作完了。最後再@「1」。有着必然前後順序。ide

  1. 異步 + 主隊列 (讓機器人充電準備尋寶 放到 強行自身去作的任務表 )
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
//    [self syncConcurrent]
//    [self syncSerial];
    [self asyncMain];
    NSLog(@"1---%@", [NSThread currentThread]);
}

//異步 + 主隊列
- (void)asyncMain{
    //獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"---start---");
    //使用異步函數封裝三個任務
    dispatch_async(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    NSLog(@"---end---");
}
複製代碼

和異步 + 串行隊列區別就是不開啓新線程。 函數

異步 + 主隊列
讓機器人充電準備,因此本身先吃喝拉撒完。直到@"1"。 而後你發現這件事是在強制本身作的任務表上,因而就本身一件接一件作了。

  1. 同步+主隊列(死鎖)(吃喝拉撒 + 強行自身去作)
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
//    [self syncConcurrent]
//    [self syncSerial];
//    [self asyncMain];
    [self syncMain];
    NSLog(@"1---%@", [NSThread currentThread]);
}
//同步+主隊列(死鎖)
- (void)syncMain{
    //獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"---start---");
    //使用同步函數封裝三個任務
    dispatch_sync(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務B---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務C---%@", [NSThread currentThread]);
    });
    NSLog(@"---end---");
}
複製代碼

同步+主隊列(死鎖)
這裏認真分析一下死鎖的緣由,不用那個例子了。 先說點計算機組成原理的知識,雖然我也學得很爛。 計算機指令包括操做碼和地址碼。 每一個函數進入都會記住進入的地址碼,return時就會回去。

上面主隊列在主隊列中加了任務。 實質在同一個同步串行隊列中,再使用該串行隊列同步的執行任務。

[self syncMain]這是主隊列作(出)的事(同步且未作完)。根據先進先出,主隊列頭是syncMain。而後假設這裏的內存地址是1。

dispatch_sync(queue, ^{
        NSLog(@"任務A---%@", [NSThread currentThread]);
    });
// 假設運行時此處內存地址爲1
複製代碼

添加了一個block到主隊列尾部,要等主隊列頭synMain執行完才能執行。 原本應該執行追加任務B,可是電路上的地址並無回來,由於dispatch_sync要執行完block才reutrn。 由於被代碼被黑盒子包起來了,大膽猜想一下。 假設內存地址爲2

// 調用時記住進入地址爲1
dispatch_sync {
  // block執行完才return
  // 運行時此處內存地址爲2
   if( block() ) { // block執行完
     return;//返回到進入地址
   }
}
複製代碼

因而代碼能夠當作 卡在了該函數內部,內存地址爲2處。 沒有回到1處,天然就不會追加任務B。

開發中經常使用的作法

上面說了不少種方法,禁止死鎖狀況開發中是很容易記住的。 但其餘組合,即便想的時候能想懂,但也仍是很混亂。 根據我我的經驗,平常開發中先從宏觀上想是否須要耗時(耗時放到子線程),是否有序。 一般是須要和主線程同時執行(開新線程,即異步執行任務)纔會用到GCD。 多是開發經驗不夠。

  • 異步 + 並行或串行。 舉個例子。

從子線程,異步返回主線程更新UI。 隊列經常使用全局並行隊列。 由於要下載圖片耗時,並且具備網絡不穩定性,因此放到子線程。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3948453733,2367168123&fm=27&gp=0.jpg"]];
        UIImage *image = [UIImage imageWithData:imgData];
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        dispatch_async(mainQueue, ^{
            UIImageView *imgView = [[UIImageView alloc]initWithImage:image];
            [imgView setFrame:CGRectMake(0, 0, 200, 200)];
            [imgView setCenter:self.view.center];
            [self.view addSubview:imgView];
        });
    });

複製代碼
  • 隊列組 隊列組能獲知隊列完成程度。 同時下載多個圖片,全部圖片下載完成以後去更新UI。
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
//    [self syncConcurrent]
//    [self syncSerial];
//    [self asyncMain];
//    [self syncMain];
    [self groupTest];
    NSLog(@"1---%@", [NSThread currentThread]);
}

- (void)groupTest {
    dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_group_t groupQueue = dispatch_group_create();
    NSLog(@"current task");
    dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
        NSLog(@"並行任務1");
    });
    dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
        NSLog(@"並行任務2");
    });
    dispatch_group_notify(groupQueue, mainQueue, ^{
        NSLog(@"groupQueue中的任務 都執行完成,回到主線程更新UI");
    });
    NSLog(@"next task");
}
複製代碼

隊列組測試
1. dispatch_group_t groupQueue = dispatch_group_create();用於生成隊列組 2.生成隊列時加上前綴_guoup 3. dispatch_group_notify這個函數用以處理其餘隊列完成的塊。

GCD其餘的API

  • dispatch_once:這個函數保證在應用程序執行中只執行一次指定處理的API。(見過用於音樂播放器單例)
static dispatch_once_  onceToken;

dispatch_once( &onceToken,^{

對象A =[ [對象A  alloc] init];

});
複製代碼
  • dispatch_barrier_async:柵欄方法。用於在同一個隊列中,阻斷先後的任務。
- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self asyncConcurrent];
//    [self asyncSerial];
//    [self syncConcurrent]
//    [self syncSerial];
//    [self asyncMain];
//    [self syncMain];
//    [self groupTest];
    [self barrier];
    NSLog(@"1---%@", [NSThread currentThread]);
}

// 欄柵函數
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任務A---%@",[NSThread currentThread]);      
    });
    dispatch_async(queue, ^{
        NSLog(@"任務B---%@",[NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"欄柵函數---%@",[NSThread currentThread]);
    });
//  換成同步執行也同樣
//  dispatch_barrier_sync(queue, ^{
//        NSLog(@"欄柵函數---%@",[NSThread currentThread]);
//    });
    dispatch_async(queue, ^{
        NSLog(@"任務C---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務D---%@",[NSThread currentThread]);
    });
}
複製代碼

欄柵函數
能夠這麼理解
image.png

  • dispatch_after:延時執行方法,時間並不精準。我經常使用其餘延時方法,不展開談論這個。
  • dispatch_apply:快速迭代方法。 for必須按順序同步遍歷,dispatch_apply能夠同時遍歷多個數字。至關於開線程遍歷。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(10, queue, ^(size_t i) {
        NSLog(@"%zd----%@", i, [NSThread currentThread];
    }
複製代碼
  • 還有一些別的不經常使用就不展開了。

GCD會遇到的問題

  • 死鎖 上面解釋過了
  • 線程安全 場景:兩條不一樣的線程之間同時對一個數據I/O。 好比商品數量 count = 10 , 單價price = 2 單件重量 = 0.1 A線程要取某個商品數量,算出商品總價,商品總重量。 B線程修改商品數量。 假如A先算出商品總價20,這時B忽然修改了count = 11,那A算出的重量是1.1,而不是指望的10。 解決方法: 先簡單理解線程和runloop。主線程一定會開一條runloop。但子線程默認是不開啓的。開啓了runloop就會執行某個機制,讓線程在循環,不至於銷燬。 因此咱們能夠在A訪問到count時,對count加鎖,別的線程只能夠取值,不能夠寫入。這時別的線程若是訪問不到,就會開啓runloop,不定時訪問,看看count解鎖沒有。 加鎖方法 方法一 互斥鎖(同步鎖)
@synchronized(鎖對象) {
    // 須要鎖定的代碼
}
複製代碼

判斷的時候鎖對象要存在,若是代碼中只有一個地方須要加鎖,大多都使用self做爲鎖對象,這樣能夠避免單獨再建立一個鎖對象。 方法二:自旋鎖 用到屬性修飾原子屬性nonatomicatomic非原子屬性

  • atomic:保證同一時間只有一個線程可以寫入,讀取隨意
  • nonatomic:同一時間能夠有不少線程讀和寫 atomic帶有自旋鎖,別的線程若是寫入,就會開啓runloop。 可是循環執行的線程,會消耗很多資源。因此通常開發中,除非肯定否則不要用atomic。

參考

力薦第三篇,看了不少瞎說的,就這篇真實!!!

相關文章
相關標籤/搜索