你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS多線程編程(四)------ GCD(Grand Central Dispatch)

iOS多線程編程(四)------ GCD(Grand Central Dispatch)

編輯:IOS開發綜合

一、簡介

是基於C語言開發的一套多線程開發機制,也是目前蘋果官方推薦的多線程開發方法,用起來也最簡單,只是它基於C語言開發,並不像NSOperation是面向對象的開發,而是完全面向過程的。如果使用GCD,完全由系統管理線程,我們不需要編寫線程代碼。只需定義想要執行的任務,然後添加到適當的調度隊列(dispatch_queue).GCD會負責創建線程和調度你的任務,系統會直接提供線程管理。

二、任務和隊列

GCD中有兩個核心概念

(1)任務:執行什麼操作

(2)隊列:用來存放任務

GCD的使用就兩個步驟

(1)定制任務

(2)確定想做的事情

將任務添加到隊列中,GCD會自動將隊列中的任務取出,放到對應的線程中執行

提示:任務的取出遵循隊列的FIFO原則:先進先出,後進後出

三、執行任務

1、GCD中有2個用來執行任務的函數

說明:把右邊的參數(任務)提交給左邊的參數(隊列)進行執行

(1)用同步的方式執行任務 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

參數說明:queue:隊列; block:任務

(2)用異步的方式執行任務 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

2、同步和異步的區別

同步:在當前線程中執行

異步:在另一條線程中執行

四、隊列

1、GCD的隊列可以分為2大類型

(1)並發隊列(Concurrent Dispatch Queue):可以讓多個任務並發(同時)執行(自動開啟多個線程同時執行任務)並發功能只有在異步(dispatch_async)函數才有效

(2)串行隊列(Serial Dispatch Queue):讓任務一個接著一個地執行(一個任務執行完畢後,再執行下一個任務)

2、補充說明

有4個術語比較容易混淆:同步、異步、並發、串行(在博客 多線程編程(-)—-概念 也提到了)

同步和異步決定了要不要開啟新的線程

同步:在當前線程中執行任務,不具備開啟新線程的能力

異步:在新的線程中執行任務,具備開啟新線程的能力

並發和串行決定了任務的執行方式

並發:多個任務並發執行

串行:一個任務執行完畢後,再執行下一個任務

五、(同步/異步)串行隊列和(同步/異步)並發隊列開啟線程的總結 (代碼示例)

說明:

(1)同步函數不具備開啟線程的能力,無論是什麼隊列都不會開啟線程;異步函數具備開啟線程的能力,開啟幾條線程有隊列決定(串行隊列只會開啟一條新的線程,並發隊列會開啟多條線程)

(2) MRC下凡是函數中,各種函數名中帶有create\copy\new\retain等字眼,都需要在不需要使用這個數據的時候進行release

ARC下GCD的數據類型不需要再作release

CF(core Foundation)的數據在ARC環境下還是需要release

(3) 異步函數具備開線程的能力,但不一定會開線程

1、異步並發隊列(同時開啟N個線程)

 

這裡寫圖片描述
/**
 *  異步並發隊列
 */
- (void)asynchronousConcurrent{
    NSLog(@"異步函數往並發隊列中添加任務");
    NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);

    // 1、創建並發隊列
    // 方法一 和創建串行隊列一樣
    //    dispatch_queue_t queue = dispatch_queue_create("asynConcurrent", DISPATCH_QUEUE_CONCURRENT);
    // 方法二 獲取全局並發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 2、添加任務到隊列
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1 ------ %@", [NSThread currentThread]);
        [self loadImage:1];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片2------ %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片3 ------ %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);


    // 打印結果 開啟多個線程,並發執行。沒有先後順序(有的話也是剛好巧合而已) 可以看number 可以看做線程值
    /**
     第一次執行
     2016-08-24 12:53:24.026 YJGCDDemo[1220:24092] 異步函數往並發隊列中添加任務
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主線程1111 ---- {number = 1, name = main}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主線程2222 ---- {number = 1, name = main}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24126] 下載圖片1 ------ {number = 2, name = (null)}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24128] 下載圖片2------ {number = 3, name = (null)}
     2016-08-24 12:53:24.027 YJGCDDemo[1220:24127] 下載圖片3 ------ {number = 4, name = (null)}

     第二次執行
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 異步函數往並發隊列中添加任務
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主線程1111 ---- {number = 1, name = main}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主線程2222 ---- {number = 1, name = main}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24126] 下載圖片2------ {number = 2, name = (null)}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24260] 下載圖片3 ------ {number = 7, name = (null)}
     2016-08-24 12:53:32.427 YJGCDDemo[1220:24153] 下載圖片1 ------ {number = 6, name = (null)}

     */
}

2、異步串行隊列(會開啟線程,但是只開啟一個線程)
這裡寫圖片描述

/**
 *  異步串行隊列
 */
- (void)asynchronousSerial{

    NSLog(@"用異步函數往串行隊列中添加任務");
    NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);

    //1. 創建串行隊列
    dispatch_queue_t queue = dispatch_queue_create("asynSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"下載圖片1 --- %@", [NSThread currentThread]);
        [self loadImage:1];
    });


    dispatch_async(queue, ^{
        NSLog(@"下載圖片2 --- %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_async(queue, ^{
        NSLog(@"下載圖片3 --- %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);


    // 打印結果:異步串行隊列,會開啟一個線程,順序執行。 看運行結果也可以看出,圖片是一張下載完在下載下一張的。
    /**
     2016-08-24 12:39:14.195 YJGCDDemo[942:16507] 用異步函數往串行隊列中添加任務
     2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主線程1111 ---- {number = 1, name = main}
     2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主線程2222 ---- {number = 1, name = main}
     2016-08-24 12:39:14.196 YJGCDDemo[942:16622] 下載圖片1 --- {number = 2, name = (null)}
     2016-08-24 12:39:14.261 YJGCDDemo[942:16622] 下載圖片2 --- {number = 2, name = (null)}
     2016-08-24 12:39:14.303 YJGCDDemo[942:16622] 下載圖片3 --- {number = 2, name = (null)}

     */
}

3、同步並發隊列(不會開啟新的線程,並發隊列失去並發的功能)
這裡寫圖片描述

/**
 *  同步並發隊列
 */
- (void)synchronousConcurrent{
        NSLog(@"用同步函數往並發隊列中添加任務");
        NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);

        // 1.創建並發隊列
        // 方式一 一般都使用這種方式獲取
    //    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        // 方式二 和創建串行隊列一樣
        dispatch_queue_t queue = dispatch_queue_create("syncConcurrentncy", DISPATCH_QUEUE_CONCURRENT);

        // 2.加添任務到隊列
        dispatch_sync(queue, ^{
            NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
            [self loadImage:1];
        });

        dispatch_sync(queue, ^{
            NSLog(@"下載圖片2 ---- %@", [NSThread currentThread]);
            [self loadImage:2];
        });

        dispatch_sync(queue, ^{
            NSLog(@"下載圖片3 ---- %@", [NSThread currentThread]);
            [self loadImage:3];
        });

        NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);


    // 打印結果 和同步串行隊列一樣 這邊並發隊列效果失效,不會開啟線程。
    /**
     2016-08-24 11:20:44.153 YJGCDDemo[43913:3002870] 用同步函數往並發隊列中添加任務
     2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 主線程1111 ---- {number = 1, name = main}
     2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 下載圖片1 ---- {number = 1, name = main}
     2016-08-24 11:20:44.433 YJGCDDemo[43913:3002870] 下載圖片2 ---- {number = 1, name = main}
     2016-08-24 11:20:44.475 YJGCDDemo[43913:3002870] 下載圖片3 ---- {number = 1, name = main}
     2016-08-24 11:20:44.508 YJGCDDemo[43913:3002870] 主線程2222 ---- {number = 1, name = main}

     */
}

4、同步串行隊列(不會開啟新的線程)
這裡寫圖片描述

/**
 *  同步串行隊列
 */
- (void)synchronousSerial{
    NSLog(@"用同步函數往串行隊列中添加任務");
    NSLog(@"主線程111----- %@", [NSThread currentThread]);

    // 1、創建串行隊列 // DISPATCH_QUEUE_SERIAL 串行隊列 也可以為NULL NULL時 默認是 串行隊列; DISPATCH_QUEUE_CONCURRENT 並發隊列
    dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);

    // 2、添加任務到隊列中執行
    dispatch_sync(queue, ^{
        // 此塊代碼沒有任何意義,只是為了體現同步串行隊列的效果, 只能執行完了 才能執行面下的,
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
        [self loadImage:1];
    });

    dispatch_sync(queue, ^{
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片2 -- %@", [NSThread currentThread]);
        [self loadImage:2];
    });

    dispatch_sync(queue, ^{
        for (int i = 0; i < 30000; i++) {
            //
            NSLog(@"%i", i);
        }
        NSLog(@"下載圖片3 -- %@", [NSThread currentThread]);
        [self loadImage:3];
    });

    NSLog(@"主線程222----- %@", [NSThread currentThread]);


    // 打印結果就是同步按順序執行。 每個任務按順序執行,不開啟線程
    /**
     2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 用同步函數往串行隊列中添加任務
     2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 主線程111----- {number = 1, name = main}
     2016-08-24 10:52:55.050 YJGCDDemo[43413:2986818] 下載圖片1 ---- {number = 1, name = main}
     2016-08-24 10:52:55.580 YJGCDDemo[43413:2986818] 下載圖片2 -- {number = 1, name = main}
     2016-08-24 10:52:55.616 YJGCDDemo[43413:2986818] 下載圖片3 -- {number = 1, name = main}
     2016-08-24 10:52:55.644 YJGCDDemo[43413:2986818] 主線程222----- {number = 1, name = main}

     */
}

六、常用方法 在Demo中的CommonMethodsViewCotroller類

1、後台運行

 dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // something
    });

2、主線程執行

dispatch_async(dispatch_get_main_queue(), ^{
        // something
    });

3、一次性執行

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // code to be execution once
        NSLog(@"改行代碼只執行一次");
    });

4、延遲N秒執行 (這邊列舉了四種方式)
這裡寫圖片描述

    NSLog(@"打印線程----- %@", [NSThread currentThread]);
    // 延時執行方式一 使用NSObject的方法
    // 2秒後再調用self的run方法
//    [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];

    // 延遲執行方式二 使用GCD函數
       // 在同步函數中執行
        // 注意 如果使用異步函數 dispatch_async 那麼[self performSelector:@selector(loadImage) withObject:nil afterDelay:5.0]; 不會被執行
//    dispatch_queue_t queue = dispatch_queue_create("yangjian.net.cn", 0);
//    dispatch_sync(queue, ^{
//        [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];
//    });

    // 延遲執行方式三 可以安排其線程---> 主隊列
//    dispatch_queue_t queue = dispatch_get_main_queue();
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
//        NSLog(@"主隊列--延遲執行------%@",[NSThread currentThread]);
//        [self gcdLoadImage];
//    });

    // 延遲執行方式四 可以安排其線程---> 並發隊列
    //1、獲取全局並發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //2、計算任務執行的時間
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    //3、會在when這個時間點,執行queue中的這個任務
    dispatch_after(when, queue, ^{
        NSLog(@"並發隊列--延遲執行 ---- %@", [NSThread currentThread]);
        [self gcdLoadImage];
    });

5、執行某個代碼片段N次

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(4, globalQueue, ^(size_t index) {
        // 執行4次
    });

6、隊列組的使用

    // 需求
    //1  分別異步執行2個耗時的操作
    //2  等兩個異步操作都執行完畢後,再回到主線程執行操作

//   如果想要快速高效地實現上述需求,可以考慮用隊列組

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        // 並發執行的線程一
    });

    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        // 並發執行的線程二
    });

    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        // 等前面的異步操作都執行完畢後,回到主線程
    });

七、代碼示例 在Demo中的CommonMethodsViewCotroller類
下載兩張照片完後,合並照片。(兩種方式)
這裡寫圖片描述

/**
 *  合並圖片(方式一)
 */
- (void)mergeImage{

//    // 獲取全局並發隊列
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
//    // 獲取主隊列
//    dispatch_queue_t mainQueue = dispatch_get_main_queue();


    dispatch_async(global_queue, ^{
        // 下載圖片1
        UIImage *image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
        NSLog(@"圖片1下載完成----%@", [NSThread currentThread]);

        UIImage *image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];

        NSLog(@"圖片2下載完成----%@", [NSThread currentThread]);

        // 回到主線程顯示圖片
        dispatch_async(main_queue, ^{
            NSLog(@"顯示圖片---%@", [NSThread currentThread]);
            self.imageViewOne.image = image1;
            self.imageViewTwo.image = image2;

            // 合並兩張圖片
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
            [image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
            [image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
            self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
            // 關閉上下文
            UIGraphicsEndImageContext();

            NSLog(@"圖片合並完成----%@", [NSThread currentThread]);

        });
    });

    // 打印結果 需要等圖片1下載完 在下載圖片2 再回到主線程
    /**
     2016-08-24 16:58:40.123 YJGCDDemo[3480:125975] 圖片1下載完成----{number = 3, name = (null)}
     2016-08-24 16:58:40.319 YJGCDDemo[3480:125975] 圖片2下載完成----{number = 3, name = (null)}
     2016-08-24 16:58:40.319 YJGCDDemo[3480:125910] 顯示圖片---{number = 1, name = main}
     2016-08-24 16:58:40.335 YJGCDDemo[3480:125910] 圖片合並完成----{number = 1, name = main}
     */
    // 效率不高 需要等圖片1,圖片2都下載完了後才合並
    // 優化 使用隊列組可以讓圖片1 圖片2的下載任務同事進行,且當兩個下載任務都完成的時候回到主線程進行顯示。


}
/**
 *  使用隊列組解決(方式二)
 */
- (void)groupMergeImage{
    //步驟
    //  1、創建一個隊列組
    //  2、開啟一個任務下載圖片1
    //  3、開啟一個任務下載圖片2
    //  同時執行下載圖片1  和 下載圖片2操作
    //  4、等group中的所有任務都執行完畢,再回到主線程執行其他操作

    // 1、創建一個隊列租
    dispatch_group_t group = dispatch_group_create();

    // 2、開啟一個任務下載圖片1
    __block UIImage *image1 = nil;
    dispatch_group_async(group, global_queue, ^{
        image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
        NSLog(@"圖片1下載完成--- %@", [NSThread currentThread]);
    });

    // 3、開啟一個任務下載圖片2
    __block UIImage *image2 = nil;
    dispatch_group_async(group, global_queue, ^{
        image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];
        NSLog(@"圖片2下載完成--- %@", [NSThread currentThread]);
    });

    // 同時執行下載圖片1\下載圖片2操作

    // 4、等group重的所有任務都執行完畢,再回到主線程執行其他操作
    dispatch_group_notify(group, main_queue, ^{
        NSLog(@"顯示圖 --- %@", [NSThread currentThread]);
        self.imageViewOne.image = image1;
        self.imageViewTwo.image = image2;

        // 合並兩張圖片
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
        [image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
        [image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
        self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
        // 關閉上下文
        UIGraphicsEndImageContext();

        NSLog(@"圖片合並完成 --- %@", [NSThread currentThread]);
    });


    // 同時開啟兩個線程 分別下載圖片 會比上面的效率高一點
    /**
     2016-08-24 16:58:03.785 YJGCDDemo[3467:125346] 圖片1下載完成--- {number = 3, name = (null)}
     2016-08-24 16:58:03.978 YJGCDDemo[3467:125349] 圖片2下載完成--- {number = 4, name = (null)}
     2016-08-24 16:58:03.978 YJGCDDemo[3467:125303] 顯示圖 --- {number = 1, name = main}
     2016-08-24 16:58:03.995 YJGCDDemo[3467:125303] 圖片合並完成 --- {number = 1, name = main}

     */

}

八、線程間通信

// 從子線程回到主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     // 執行耗時的異步操作
     dispatch_async(dispatch_get_main_queue(), ^{
          //回到主線程,執行UI刷新操作
     });
 });

九、Operation和GCD的對比

優點: 不需要關心線程管理,數據同步的問題;

區別:

1、性能:GCD更接近底層,而NSOperation則更高級抽象,所以GCD在追求性能的底層操作來說,是速度最快的。

2、從異步操作之間的事務性,順序行,依賴關系。GCD需要自己寫更多的代碼來實現,而NSOperationQueue已經內建了這些支持

3、如果異步操作的國學更多的被交互和UI呈現出來,NSOperationQueue會是一個更好的選擇。底層代碼中,任務之間不太互相依賴,而需要更高的並發能力,GCD則更有優勢

十、總結

學了兩天,對gcd有一些的了解,能在項目中使用多線程,不過這邊也要避免很多死鎖的問題,後期有時間再整理出來。四天左右把iOS多線程的幾種方法都整理了下,寫了demo。也算對自己的一個小小總結。

總結兩點:

1、線程運行方式

dispatch_async 異步執行

dispatch_sync 同步執行

dispatch_delay 延遲執行

2、處理任務對象

dispatch_get_main_queue 主線程隊列(UI線程隊列)

dispatch_get_global_queue 並發線程隊列

串行隊列

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved