iOS學習筆記-----GCD 用法介紹

GCD(Grand Central Dispatch)

簡介

  • Apple提供的一套更底層、更高效的併發編程技術,純C語言、基於Block
  • 支持同步或異步任務處理,串行、並行的處理隊列,非系統調用的信號量機制,定時任務處理,進程、文件或網絡的監聽任務等

優點

  • 易用:GCD比之thread更簡單易用。基於block的特性導致它能極爲簡單得在不同代 碼作用域之間傳遞上下文
  • 效率:GCD實現功能 輕量、優雅,使得它在很多地方比之專門創建消耗資源的線程 更實用且快速
  • 性能:GCD自動根據系統負載來增減線程數量,這就減少了上下文切換以及增加了計 算效率
  • 安全:無需加鎖或其他同步機制

Dispatch Queue

兩種隊列

Dispatch Queue是執行處理的等待隊列。通過dispatch_async等函數, 按照先進先出(FIFO)順序追加到Queue中處理,執行處理時,存在兩種 Dispatch Queue:

  • Serial Dispatch Queue :

    串行隊列,一個線程同時執行一個任務,可以避免數據競爭的問題
    可以生成多個 Serial Dispatch Queue,各個 Serial Dispatch Queue 將並行執行

  • Concurrent Dispatch Queue :

    併發隊列,多個線程同時執行多個任務,效率高,具體是多少個線程併發執行,取決於CPU核數和CPU負荷

主隊列與全局隊列

  • Main Dispatch Queue

    主隊列,在主線程裏執行的隊列。因爲主線程只有一個,所以 Main Dispatch Queue 自然就是 Serial Dispatch Queue.
    一切跟UI有關的操作必須放在主線程中執行,所以要追加到Main Dispatch Queue.
    (dispatch_get_main_queue)

  • Global Dispatch Queue

    全局隊列,所有應用程序都能夠使用的 Concurrent Dispatch Queue.
    (dispatch_get_global_queue)

GCD使用

1.兩種任務添加方式

  • dispatch_async 異步添加

    提交一個異步執行的 block塊 到隊列裏面並且直接返回,不用等待
    block 被調用

  • dispatch_sync 同步添加

    提交一個同步執行的 block塊 到隊列裏面並且等待,直到這個 block 執行完成,與 dispatch_async 相反
    注:使用 dispatch_sync 容易引起死鎖,慎重使用。比如在主線程裏面執行 往主隊列裏面添加 任務 的操作就會引起死鎖

2.使用GCD簡單實現UIImageView異步加載圖片

  • 創建類目
  • 自定義方法
- (void)setImageWithURL:(NSURL *)url {

    //創建串行隊列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);

    //依次將兩個任務 異步的添加到串行隊列中
    //局部變量 只有添加了__blcok 才能在block中被修改
    __block UIImage *image = nil;
    //開啓多線程 加載網絡圖片
    dispatch_async(queue, ^{
        //從網絡讀取數據
        NSData *data = [NSData dataWithContentsOfURL:url];
        image = [UIImage imageWithData:data];
    });
    //回到主線程 顯示圖片到視圖中
    dispatch_async(queue, ^{
        //重新獲取主隊列,並且將顯示圖片的操作,添加到主隊列執行
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        //向主隊列中添加界面刷新操作
        dispatch_async(mainQueue, ^{
            self.image = image;
        });

    });

}

3.延遲任務 dispatch_after


    /*  獲取某一個時間點
     *  @param when    參照時間   DISPATCH_TIME_NOW = 當前時間點
     *  @param delta   時間差 納秒爲單位的時間  NSEC_PER_SEC = 1秒
     *  @return 時間點
     */
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3);

//延時調用異步任務
dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"延遲任務執行");
     });
  • dispatch_after 和 performSelector:withObject:afterDelay: 的區別

1. 調用方式,前者使用block形式來調用,後者只能使用Selector
2. 後者能夠通過cancel來取消還未開始的方法掉用,但是dispatch_after需要通過非常複雜的方法才能夠來取消延遲任務。
3. dispatch_after的精確度比後者高很多

4.設置隊列優先級

dispatch_set_target_queue(<#dispatch_object_t object#>, <#dispatch_queue_t queue#>);
//改變queue的優先級與目標queue相同
//可以使多個serial queue在目標queue上 一次只有一個執行(串行執行)
/*
優先級
DISPATCH_QUEUE_PRIORITY_HIGH 2     高優先級
DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默認優先級
DISPATCH_QUEUE_PRIORITY_LOW (-2)   低優先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND 後臺執行
*/

5.掛起隊列

    //將隊列處於懸停狀態,在懸停狀態的隊列,無法繼續執行其中的任務。但是對已經開始執行的任務,無效
    //創建串行隊列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    //掛起隊列
    dispatch_suspend(queue);
    //在5秒鐘之後 再進行恢復
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5);
    dispatch_after(time, dispatch_get_main_queue(), ^{
       //恢復隊列
        dispatch_resume(queue);
    });

  • 掛起任務 dispatch_suspend

    掛起(暫停)隊列裏面尚未開始執行的任務,對已經正在執行的任務沒有影響
    使 queue 的 suspension reference count 加1

  • 恢復任務 dispatch_resume
    恢復隊列裏面之前被掛起的任務,使這些任務能夠繼續執行
    使 queue 的 suspension reference count 減1

  • 注意事項
    當suspension reference count大於0時,queue就保持掛起狀態。因此,必須平 衡使用suspend和resume
    如果掛起了一個queue或者source,那麼在銷燬它之前,必須先對其進行恢復

6.一次任務

保證 dispatch_once 中的代碼塊在應用程序裏面只執行一次,無論是不是多線程。

所以可以用來實現單例模式,安全、簡潔、方便。

#import "Person.h"

@implementation Person
static Person *p;
+ (instancetype)sharedPerson {


    if (p == nil) {
        //dispatch_once 一次任務 多用於單例對象
        //使用dispatch_once執行的代碼,在整個程序的運行過程中,一共只能執行一次
        //onceToken 表示執行的標記
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            p = [[super allocWithZone:nil] init];
        });
    }

    return p;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    if (p == nil) {
        p = [Person sharedPerson];
    }

    return p;
}

- (id)copy {
    return self;
}

@end

7.組任務

1.dispatch_group_async 監視一組block任務的完成,多個任務都結束後 的一個彙總處理,可以同步或異步地監視
2.dispatch_group_notify 所有任務執行結束彙總,不阻塞當前線程
3.dispatch_group_wait 等待直到所有任務執行結束,中途不能取消,阻塞當前線程

具體代碼:

    //創建隊列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //創建任務組
    dispatch_group_t group = dispatch_group_create();

    //1.添加任務到隊列中,並且添加到任務組中
    dispatch_group_async(group, globalQueue, ^{

        NSLog(@"任務1開始");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任務1結束");
    });


    //2.在組中所有的任務完成後,會收到一個完成的通知,然後來調用相對應的Block
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"任務已經完成");
    });


    //3.監控任務是否完成
    //設定時間點
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 8);
    //等待  等待到某個時間點後,來查看任務的完成情況
    //等待操作,會堵塞當前線程
    //DISPATCH_TIME_FOREVER表示一直等待,直到所有任務都完成
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //返回值爲0  則代表所有任務完成,不爲0 表示有任務沒完成
    if (result == 0) {
        NSLog(@"所有任務已經執行完畢");
    } else {
        NSLog(@"還有任務沒有完成,%li", result);
    }

8.多元調用(迭代)

1. dispatch_apply 提交一個多元調用的block塊到隊列裏面,並且等待block任務的所有迭代,完成之後才返回.
2. dispatch_apply 會阻塞當前線程,推薦在 dispatch_async 中執行 dispatch_apply 函數.
3. 結合concurrent queue,dispatch_apply能實現一個高性能的循環迭代.

    NSMutableArray *array = [NSMutableArray arrayWithCapacity:1000];
     for (int i = 0; i < 1000; i++) {
        [array addObject:@(i)];
    }
    //多元調用(迭代)
    //創建併發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //在全局隊列中 異步的添加迭代任務
    dispatch_async(queue, ^{

        //多元調用
        /**
         *  多元調用 每一次調用之間沒有順序
         *
         *  @param iterations   調用次數
         *  @param queue        調用所在隊列  併發隊列
         *  @param block        每一次調用 所執行的Block
         *
         */
        dispatch_apply(array.count, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:0.3];
            NSLog(@"%@", array[index]);
            NSLog(@"%@", [NSThread currentThread]);
        });

    });


    /*
     爲了提高大數據量的數組遍歷的速度,所以使用多元調用
     for循環遍歷,每一次遍歷操作,串行執行。總時間 = 每一次遍歷的耗時 * 總次數
     使用多元調用,將每一次遍歷,放到多線程中去。在多線程中,可以同時遍歷多次,這樣能夠提高遍歷的效率
     總時間 = 每一次遍歷的耗時 * 總次數 / 線程數 + 線程開啓關閉的時間

     !!  遍歷的過程,沒有順序不按照數組順序來
     */

9.設立障礙

1.dispatch_barrier_async 提交一個異步執行的帶障礙的block塊到隊列裏面,並且直接返回.
2.使用 dispatch_barrier_async,需要指定通過dispatch_queue_create函數創建的一個concurrent queue.
3.在barrier任務之前的所有任務將並行執行,任何在此之後提交的任務將不會執行直到這個 barrier任務執行完成.

 //添加障礙任務
    //創建併發隊列 不能使用全局隊列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);

    //異步的添加任務1
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"任務1 %i", i);
        }
    });

 //異步的添加任務2
  dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"任務2 %i", i);
        }
    });
 //插入障礙任務
    dispatch_barrier_async(queue, ^{
        NSLog(@"障礙任務開始");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"障礙任務結束");
    });

 //異步的添加任務3
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"任務3 %i", i);
        }
    });

 //異步的添加任務4
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"任務4 %i", i);
        }
    });


//任務1,2執行完成後,要執行障礙任務,等待障礙任務完成,才能繼續執行任務3,4.

這裏寫圖片描述

10.信號量semaphore


1.dispatch_semaphore_t持有計數的信號,使用計數來實現該信號功能。計數爲0時等待,計數大於等於1時, 減去1而不等待
2.dispatch_semaphore_create 創建新的計數信號
3.dispatch_semaphore_wait 信號量爲0時等待,大於等於1時,減1而不等待
4.dispatch_semaphore_signal 發信號,使信號加1

//使用信號量 來設置同時訪問資源的線程數
    //創建隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    __block NSMutableArray *array = [[NSMutableArray alloc] init];
    //創建信號量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    //循環插入數據
    for (int i = 0; i < 100000; i++) {
        dispatch_async(queue, ^{

            /**
             * 判斷信號量 是否能夠繼續執行 訪問公共資源
             * 如果此時信號量不爲0  則信號量減1,然後繼續執行代碼
             * 信號量爲0 則等待,直到時間超時或者信號量大於0
             * 保證信號量爲0時,其他線程不能訪問
             */
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

            [array addObject:@(i)];

            //公共資源訪問完畢,釋放信號量
            dispatch_semaphore_signal(semaphore);
        });
    }

多線程總結

GCD pk NSOperationQueue

  • GCD是純C語言的API;NSOperationQueue是基於GCD的OC版本的封裝
  • GCD的執行速度比NSOperationQueue快
  • GCD只支持FIFO的隊列,且任務一旦添加到隊列則無法取消;NSOperationQueue 可以很方便的調整執行順序,可以添加依賴,設置最大併發量
  • NSOperationQueue支持KVO,可以檢測Operation的狀態(執行、結束、取消)

GCD or NSOperationQueue

  • GCD本身非常簡單、易用、效率高,對於不復雜的多線程操作,會節省代碼量,而Block參數的使用,會是代碼更爲易讀,建議在簡單項目中使用
  • NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化NSOperation的設計思路,是具有面向對象的優點(複用、封裝),使得實現是多線程支持,而接口簡單,建議在複雜項目中使用

Sleep和Wait的區別

1.sleep是NSThread中的一個方法,wait是線程鎖中的一個方法.
2.sleep用於線程控制,使某一個線程進入到休眠狀態.wait用於線程通信,使一個線程進入到等待狀態,但是當另一個線程解鎖時,會喚醒當前等待的線程.
3.sleep不會釋放線程,在休眠狀態中,會一直佔用當前線程.wait會釋放當前線程,將線程空閒出來.當wait被喚醒時,會重新獲取線程的控制權.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章