OC多線程之GCD

要了解多線程首先要知道什麼是進程,什麼是進程?算法

正在進行中的程序被稱爲進程,負責程序運行的內存分配
每個進程都有本身獨立的虛擬內存空間
 
什麼是線程:
線程是進程中一個獨立的執行路徑(控制單元)
一個進程中至少包含一條線程,即主線程
能夠將耗時的執行路徑(如:網絡請求)放在其餘線程中執行
建立線程的目的就是爲了開啓一條新的執行路徑,運行指定的代碼,與主線程中的代碼實現同時運行
 
線程的優缺點:
優點
(1)充分發揮多核處理器優點,將不一樣線程任務分配給不一樣的處理器,真正進入「並行運算」狀態
(2)將耗時的任務分配到其餘線程執行,由主線程負責統一更新界面會使應用程序更加流暢,用戶體驗更好
(3)當硬件處理器的數量增長,程序會運行更快,而程序無需作任何調整
弊端
新建線程會消耗內存空間和CPU時間,線程太多會下降系統的運行性能
誤區
多線程技術是爲了併發執行多項任務,不會提升單個算法自己的執行效率
 
iOS開發中有哪些多線程技術?
NSThread
(1)使用NSThread對象創建一個線程很是方便
(2)可是!要使用NSThread管理多個線程很是困難,不推薦使用
(3)技巧!使用[NSThread currentThread]跟蹤任務所在線程,適用於這三種技術
NSOperation/NSOperationQueue
(1)是使用GCD實現的一套Objective-C的API
(2)是面向對象的線程技術
(3)提供了一些在GCD中不容易實現的特性,如:限制最大併發數量、操做之間的依賴關係
GCD —— Grand Central Dispatch
(1)是基於C語言的底層API
(2)用Block定義任務,使用起來很是靈活便捷
(3)提供了更多的控制能力以及操做隊列中所不能使用的底層函數
 
提示:iOS的開發者,須要瞭解三種多線程技術的基本使用,由於在實際開發中會根據實際狀況選擇不一樣的多線程技術
 
 
GCD的基本思想
(1)操做使用Blocks定義
(2)隊列負責調度任務執行所在的線程以及具體的執行時間
(3)隊列的特色是先進先出(FIFO)的,新添加至對列的操做都會排在隊尾
 
GCD中隊列分爲兩類,一種是串行隊列,一種是並行隊列.可是無論什麼隊列都是先添加先被分出去-----即先進先出(FIFO)
---串行隊列會將添加到隊列裏面的任務按添加順序一個一個去執行而且上一個執行完纔會執行下一個
---並行隊列則不會按照添加順序去執行任務,若是向並行隊列裏面添加了任務1,2,3,4,5,6,7,8,9,10
那麼系統會開幾條線程一併去執行這些任務,若是系統建立了4條線程去執行這10個任務,系統會先將任務1,2,3,4分配給四條線程(a,b,c,d),而後若是其中一個線程處理完一個任務,這個空閒的線程就會去執行第5個任務,可是剛纔哪一個線程把任務作完了是沒法肯定的有多是線程b有多是線程c等等都有可能
就上面的例子而言,串行隊列會只會建立一個線程去執行任務,先執行任務1,任務1完成後再執行任務2,任務2執行完成後執行任務3,以此類推.
 
 
下面咱們來用代碼看一下:
#pragma mark - 串行(一個接一個,排隊跑步,保持隊形)隊列
- (void)gcdDemo1
{
    // 將操做放在隊列中// 使用串行隊列,的異步任務很是很是很是有用!新建子線程是有開銷的,不能無休止新建線程
    // 建立一個串行隊列
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
    
    // 非ARC開發時,千萬別忘記release
//    dispatch_release(q);for (int i = 0; i < 10; ++i) {
        // 異步任務,併發執行,可是若是在穿行隊列中,仍然會依次順序執行
        dispatch_async(q, ^{
            // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
            // num = 1,表示主線程
            // num = 2,表示第2個子線程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
}

上面的代碼是建立一個串行隊列,並添加10個任務到隊列中,運行這個方法時,你會看到控制檯輸出的順序是從1到10的,證實這10個任務是一個執行完在執行另外一個,而且是按照添加順序來的網絡

 

再來看一下併發隊列裏面添加10個任務會怎樣多線程

 dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 同步任務順序執行
        dispatch_async(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

從控制檯的打印信息來看,你會看到打印是無序的,因此能夠驗證,並行隊列不是(((按照順序  && 執行完一個才執行一個的)))併發

可是前四個打印中確定有(0,1,2,3){若是開啓了四個線程的話}中的一個,由於並行隊列裏面前四個是0,1,2,3,因此前四個打印中,確定會有其中一個異步

 

同時咱們注意到上面向隊列裏面添加任務時候都是用的dispatch_async異步執行,那若是用dispatch_sync同步執行會是什麼結果呢?async

那什麼是同步操做,什麼時異步操做呢?
同步操做不會建立新的線程當前代碼執行時是哪一個線程,這些任務就會被添加到那個線程裏去執行
異步操做會建立一個新的線程,而後去執行任務(並不是絕對,若是任務被添加主隊列"主隊列是個特殊的隊列,主線程所在的隊列")
 
下面咱們來看一下並行隊列裏執行同步任務會是什麼結果
 dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 同步任務順序執行
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

根據控制檯的輸出咱們能夠看到,輸出是按順序來的,而且打印出來的Tread信息都是主線程,證實上面任務的執行都是在主線程上完成的函數

(由於因此的任務都按順序從隊列裏面出來,而且處理的線程就一個,因此會挨個執行)性能

由於這個代碼所在的線程就是主線程,因此sync就在本線程中執行(不開闢新線程)spa

 

一樣的,咱們來看一下串行隊列裏的同步任務執行結果:線程

  dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
   
    for (int i = 0; i < 10; ++i) {
        // 同步任務順序執行
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

你會看到跟上面同樣的結果

 

下面咱們再來看一下並行隊列時的一些問題

- (void)gcdDemo2
{
   
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 異步任務(第一批任務)
        dispatch_async(q, ^{
            // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
            // num = 1,表示主線程
            // num = 2,表示第2個子線程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }

    
    for (int i = 0; i < 10; ++i) {
        // 同步任務順序執行(第二批任務)
        dispatch_sync(q, ^{
            NSLog(@"%@ %dkkkkkk", [NSThread currentThread], i);
        });
    }
    
    
    
    
}

你會看到第一批任務裏面的打印會無序的打印出來,第二批任務會按照順序打印(帶有kkkk的),可是第一批跟第二批任務是穿插進行的有時候打印第一批裏面的,有時候打印第二批裏面的

而且已還回發現打印第一批任務的有好幾個線程(線程2,線程3...),,打印第二批任務的只有主線程

因此從這裏能夠看出,CPU執行任務的時候是給各個線程輪流使用的

 

可是你再看看下面的代碼

#pragma mark - 並行(並排跑,相似於賽跑)
- (void)gcdDemo2
{
    
    // 並行隊列容易出錯!並行隊列不能控制新建線程的數量!
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcd2", DISPATCH_QUEUE_CONCURRENT);
    //第一輪任務
    for (int i = 0; i < 10; ++i) {
        // 同步任務順序執行
        dispatch_sync(q, ^{
            NSLog(@"%@ %dkkkk", [NSThread currentThread], i);
        });
    }
    
    // 第二輪任務
    for (int i = 0; i < 10; ++i) {
        // 異步任務
        dispatch_async(q, ^{
            // [NSThread currentThread] 能夠在開發中,跟蹤當前線程
            // num = 1,表示主線程
            // num = 2,表示第2個子線程。。。
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
    
    
}

你會發現第一輪任務裏面的按照1,2,3,4,5,6,7.....這樣打印出來,而且第一輪任務執行完畢纔會執行第二輪任務

並且第二輪任務會無順序的打印出數字,這又爲何呢,就跟上一份代碼兩批任務的添加順序呢換了一下,結果差異怎麼那麼大呢?

由於第一輪任務使用的同步方法,什麼是同步方法呢?同步就是不會建立新的線程,當執行到那個同步任務的時候,當前的線程是誰,誰就去執行這些任務

而上面的代碼中,執行到同步任務的是主線程(num = 1),因此主線程就會按照順序去執行打印任務

 

而第二批任務是異步方法,異步方法會建立新的線程去執行任務,因此們的ThreadNum=2,3,4等等等,而且執行順序是沒法預估的(無序打印)

到這裏你可能還會有一個疑問就是爲何兩批任務不會像上一份代碼那樣,穿插着執行,而是先執行第一批在執行第二批呢?

這事由於第一批裏面用了同步而且執行到第一批的是主線程,下面咱們來詳細解釋一下

主線程是伴隨着程序運行而運行的,只要程序不掛掉,主線程就會一直運行,線程處理任務的時候是執行完一個,在執行第二個,一個線程不可能同時執行多個任務

,並且其餘線程都是由主線程去建立的,主線程就相似於一個超牛逼的人,而且他會造人技術,他須要別人幫忙的時候就會造一我的出來去幫他幹活,

可是這個超牛逼人每次只能幹一件事,幹完一件事才能幹另外一件,,,,你仔細看上面的代碼就會發現,第一批的10個任務是先添加到主線程裏面的,第二批的任務是後來出現的而且須要新的人去執行,,,,,,單此時主線程(超牛逼的那我的)已經有10個任務了,他必須先把這10個任務幹完才能去開闢一個新的線程(造人幫他幹活),因此

他會先把這10個任務完成之後,他纔會開闢新的線程去幫他執行另外的任務,,,,,,,,,從這裏就能夠很輕鬆的理解爲何先添加同步任務和先添加異步任務結果不一樣了

(先添加異步任務...上上份代碼... 就是先把造人的事情分給了主線程,然後纔給他分配的打印的任務),因此他造出來的人會和本身同時執行任務

 

 

 

由於每次建立隊列都很麻煩,因此蘋果給咱們提供了兩個能夠快速獲取的隊列---主隊列和全局隊列

下面是獲取代碼

    // 每個應用程序都只有一個主線程
    // 爲何須要在主線程上工做呢?
    // 在iOS開發中,全部UI的更新工做,都必須在主線程上執行!
    dispatch_queue_t q = dispatch_get_main_queue();


    // 全局隊列與並行隊列的區別
     // 1> 不須要建立,直接GET就能用
    // 2> 兩個隊列的執行效果相同
    // 3> 全局隊列沒有名稱,調試時,沒法確認準確隊列
    
    // 記住:在開發中永遠用DISPATCH_QUEUE_PRIORITY_DEFAULT
    // 多線程的優先級反轉!低優先級的線程阻塞了高優先級的線程!
    dispatch_queue_t q =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

上面兩行代碼分別是獲取主隊列和全局隊列的方法

添加到主隊列裏的任務會放在主線程裏執行,這裏須要注意的是,若是你使用了主隊列,必定不要使用sync同步方法,而要是用異步方法

這個事爲何呢?1.主隊列是一個串行隊列,會依次執行任務,執行完一個執行另外一個

        2.sync同步方法不會建立新的線程,會在執行到那句代碼所在的線程執行

       由於主線程會一直執行到程序關閉,因此主線程裏面的任務是執行不完的,若是在主隊列裏添加同步任務,由於主線程裏的任務是執行不玩的,因此你添加的那個任務永遠不會被執行(這裏請注意,主隊列和主線程不是一回事,線程是處理任務的, 隊列是給線程送任務的)

 

在使用主隊列時應該使用sync異步方法去執行任務,這樣不用等主線程執行完畢就能夠執行到任務,須要注意的是在主隊列裏不會建立新的線程,即便使用async一步方法也不會建立新的線程

可能上面的主隊列比較難理解,下面來看個好理解的

#pragma mark 同步任務的阻塞
- (void)gcdSyncDemo
{
    dispatch_queue_t q = dispatch_queue_create("cn.itcast.gcddemo", DISPATCH_QUEUE_SERIAL);
    
    // 任務1
    dispatch_sync(q, ^{
        NSLog(@"sync %@", [NSThread currentThread]);
        
        // 任務2,同步裏面包含同步,會形成阻塞
        dispatch_sync(q, ^{
        // 永遠也不會執行到
            NSLog(@"async %@", [NSThread currentThread]);
        });
    });
}

上面是一個任務的嵌套,由於添加順序是任務1先添加,任務2後添加,而且是同步隊列,多以任務2會等任務1完成之後再執行,可是此時任務2在任務1的裏面,若是任務2沒有執行完,任務1是不可能執行完的,此時就是任務1等任務2完成 任務2,等任務1完成,兩個任務都等待對方完成本身才能完成,這樣兩個任務都不會完成

 

而主隊列裏面添加同步任務就相似上面的狀況,主隊列裏面一個巨大的任務等待小的同步任務完成,二小的同步任務等待包含他的巨打的任務完成,形成相互等待,結果就是誰完成不了

相關文章
相關標籤/搜索