天天看點

iOS多線程開發之GCD(基礎篇)

總綱:

  • GCD基本概念
  • GCD如何實作
  • GCD如何使用
  • 隊列和任務組合

一、GCD基本概念

     GCD 全稱Grand Central Dispatch(大中樞隊列排程),是一套低層API,提供了⼀種新的方法來進⾏并發程式編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程式将任務切分為多個單一任務,然後送出⾄⼯作隊列來并發的或者串⾏的執行。GCD是C實作,⽐NSOpertionQueue更底層更高效,并且它不是Cocoa架構的一部分 并發任務會像NSOperationQueue那樣基于系統負載來合适地并發進⾏,而串⾏行隊列同一時間隻執行單一任務,GCD的API很大程度上基于block。

    GCD并發程式設計的主要好處歸納

  • GCD可用于多核的并行運算
  • GCD會自動利用更多的CPU核心(比如雙核、四核)
  • GCD會自動管理線程的生命周期(建立線程、排程任務、銷毀線程)
  • 程式員隻需要告訴GCD想要執行什麼任務,不需要編寫任何線程管理代碼

二、GCD如何實作

     GCD主要由隊列和任務兩部分來實作,蘋果官方對GCD是這樣說明的:開發者要做的隻是定義想執行的任務并追加到适當的Dispatch Queue中。Dispatch Queue是執行處理的等待隊列,我們可以通過dispatch_async等API,在block文法中記述想要執行的處理并将其追加到Dispatch Queue中,Dispatch Queue是按照追加的順序進行處理(先進先出FIFO)。

     多線程執行過程就是把任務放在隊列中去執行的過程。那麼在這裡我們首先回顧一下基本概念:

   (一)程序/線程、任務/隊列

iOS多線程開發之GCD(基礎篇)

   (二)同步/異步、并發/并行

iOS多線程開發之GCD(基礎篇)

    并發不一定等于并行

 (三)異步/同步任務 & 并行/串行隊列的特點

iOS多線程開發之GCD(基礎篇)

     綜上所述,iOS多線程程式設計使用GCD的最優原則是能不在阻礙主線程(又叫作UI線程)的情況下,開啟新的線程(子線程)去處理耗時的操作,以便有效提高程式的執行效率和資源使用率,但是同時開啟多個子線程也會引發許多其他的問題,如資源競争、死鎖、記憶體損耗等,是以要注意,這篇文章隻是介紹GCD的使用,是以可能産生的問題我将會在這個系列後續篇章做介紹。

     GCD并發程式設計産生的作用歸納(考慮線程安全,不死鎖的情況下效果):

  • 能開啟新的線程(子線程)
  • 多個任務可以同時進行
  • 不會阻塞主線程(又叫作UI線程)影響UI事件

三、GCD如何使用

    開發者要做的隻是定義想執行的任務并追加到适當的隊列(Dispatch Queue)中   

    1、建立隊列(Dispatch Queue)

    第一種:通過GCD的API的dispatch_queue_create函數生成Dispatch Queue

// 建立串行隊列
dispatch_queue_t queue= dispatch_queue_create("com.beckwang.queue", DISPATCH_QUEUE_SERIAL);

// 建立并發隊列
dispatch_queue_t queue= dispatch_queue_create("com.beckwang.queue", DISPATCH_QUEUE_CONCURRENT);      

    另外需要注意的點是:雖然有ARC編譯器自動管理記憶體這一優秀技術,但生成的Dispatch Queue必須由程式員主動釋放。

// 釋放
dispatch_release(exampleSerialDispatchQueue) 

// 持有
dispatch_retain(exampleSerialDispatchQueue)       

    第二種:直接使用系統提供的标準Dispatch Queue :Main Dispatch Queue和Global Dispatch Queue

  (1)Main Dispatch Queue:主線程中執行的Dispatch Queue,也就是Serial Dispatch Queue(串行隊列),可以通過dispatch_get_main_queue()來擷取。       

dispatch_queue_t   mainDispatchQueue = dispath_get_main_queue();      

    (2)  Global Dispatch Queue: 全局并發隊列(Concurrent Dispatch Queue),GCD預設提供了全局的并發隊列,可以通過dispatch_get_global_queue()擷取。

// 高優先級
dispatch_queue_t globalDispatchQueueHigh = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)  

// 預設優先級
dispatch_queue_t globalDispatchQueueDefault = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) 

// 低優先級
dispatch_queue_t globalDispatchQueueLow = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0)  

// 背景優先級
dispatch_queue_t globalDispatchQueueBackgroud = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_GACKGROUND,0)        

     一般來說,主線程(又叫做UI線程)主要處理UI事件,耗時操作(如I/O,資料庫通路,網絡資源加載等)則放在子線程中,等子線程操作完成後再回到主線程進行UI重新整理,以下例舉使用Main Dispatch Queue和Global Dispatch Queue的源碼:

- (void)testMainGlobalDispatchQueue{

    
    // 建立全局并發隊列,預設優先級
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       
        // 可并行處理的任務(耗時操作)代碼放在這裡
        
        
        // 擷取主線程,處理UI事件
        dispatch_async(dispatch_get_main_queue(), ^{
           
            // UI事件
            
        });
        
    });
}      

    2、建立任務

// 同步執行任務建立方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這裡放任務代碼
});

// 異步執行任務建立方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這裡放任務代碼
});      

四、隊列任務組合 

根據(二)中描述,GCD由隊列和任務兩部分組成,隊列分為串行隊列、并行隊列、主隊列,任務可分為同步和異步任務,這樣可将隊列與任務組合如下:

iOS多線程開發之GCD(基礎篇)

1、并行隊列 & 異步執行

- (void) asyncConcurrentTask
{
    NSLog(@"asyncConcurrentTask---start");

    dispatch_queue_t queue= dispatch_queue_create("com.beckwang.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"Task1------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task2------%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task3------%@",[NSThread currentThread]);
    });

    NSLog(@"asyncConcurrentTask---end");
}      

 列印結果:

2017-07-02 11:13:10.963 Test[6266:2853210] asyncConcurrentTask---start
2017-07-02 11:13:10.963 Test[6266:2853210] asyncConcurrentTask---end
2017-07-02 11:13:10.963 Test[6266:2854044] Task3------<NSThread: 0x60800007cdc0>{number = 5, name = (null)}
2017-07-02 11:13:10.963 Test[6266:2854059] Task2------<NSThread: 0x60800007d1c0>{number = 4, name = (null)}
2017-07-02 11:13:10.963 Test[6266:2854041] Task1------<NSThread: 0x600000074e80>{number = 3, name = (null)}      

  結論:

   (1) 開啟了新線程

   (2) 任務之間不需要排隊,且具有同時被執行的權利

2、并行隊列 & 同步執行

- (void)syncConcurrentTask
{
    dispatch_queue_t queue = dispatch_queue_create("com.beck.wang.queue", DISPATCH_QUEUE_CONCURRENT);
 
   NSLog(@"syncConcurrentTask---start---");
    
   dispatch_sync(queue, ^{
        NSLog(@"Task1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task3---%@", [NSThread currentThread]);
    });

    NSLog(@"syncConcurrentTask---end---");
}      

 列印結果:

2017-07-02 11:25:04.725 Test[6385:2940867] syncConcurrentTask---start---
2017-07-02 11:25:04.725 Test[6385:2940867] Task1---<NSThread: 0x608000067540>{number = 1, name = main}
2017-07-02 11:25:04.726 Test[6385:2940867] Task2---<NSThread: 0x608000067540>{number = 1, name = main}
2017-07-02 11:25:04.726 Test[6385:2940867] Task3---<NSThread: 0x608000067540>{number = 1, name = main}
2017-07-02 11:25:04.726 Test[6385:2940867] syncConcurrentTask---end---      

  結論:

   (1) 不開啟了新線程

   (2) 任務之間需要排隊,按照追加順序執行

3、串行隊列 & 異步執行

- (void)asyncSerialTask
{
    dispatch_queue_t queue = dispatch_queue_create("com.beckwang.queue", DISPATCH_QUEUE_SERIAL);
 
    NSLog(@"asyncSerialTask---start---");

    dispatch_async(queue, ^{
        NSLog(@"Task1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task3---%@", [NSThread currentThread]);
    });
    NSLog(@"asyncSerialTask---end---");
}      

 列印結果:

2017-07-02 11:36:27.068 Test[6557:3008079] asyncSerialTask---start---
2017-07-02 11:36:27.068 Test[6557:3008079] asyncSerialTask---end---
2017-07-02 11:36:27.068 Test[6557:3008342] Task1---<NSThread: 0x600000071e00>{number = 3, name = (null)}
2017-07-02 11:36:27.069 Test[6557:3008342] Task2---<NSThread: 0x600000071e00>{number = 3, name = (null)}
2017-07-02 11:36:27.069 Test[6557:3008342] Task3---<NSThread: 0x600000071e00>{number = 3, name = (null)}      

 結論:

   (1) 開啟了新線程

   (2) 任務之間需要排隊,按照追加順序執行

4、串行隊列 & 同步執行

- (void)syncSerialTask
{
    dispatch_queue_t queue = dispatch_queue_create("com.beckwang.queue", DISPATCH_QUEUE_SERIAL);
 
    NSLog(@"syncSerialTask---start---");

    dispatch_sync(queue, ^{
        NSLog(@"Task1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task3---%@", [NSThread currentThread]);
    });

    NSLog(@"syncSerialTask---end---");
}      

 列印結果:

2017-07-02 13:17:48.948 Test[7238:3192943] syncSerialTask---start---
2017-07-02 13:17:48.948 Test[7238:3192943] Task1---<NSThread: 0x600000076640>{number = 1, name = main}
2017-07-02 13:17:48.949 Test[7238:3192943] Task2---<NSThread: 0x600000076640>{number = 1, name = main}
2017-07-02 13:17:48.949 Test[7238:3192943] Task3---<NSThread: 0x600000076640>{number = 1, name = main}
2017-07-02 13:17:48.949 Test[7238:3192943] syncSerialTask---end---      

結論:

   (1) 不開啟了新線程

   (2) 任務之間需要排隊,按照追加順序執行

5、主隊列 & 異步執行

- (void)asyncMainTask
{
    dispatch_queue_t queue = dispatch_get_main_queue();
 
    NSLog(@"asyncMainTask---start---");

    dispatch_async(queue, ^{
        NSLog(@"Task1---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task2---%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"Task3---%@", [NSThread currentThread]);
    });

    NSLog(@"asyncMainTask---end---");
}      

列印結果:

2017-07-02 13:19:36.828 Test[7272:3206224] asyncMainTask---start---
2017-07-02 13:19:36.828 Test[7272:3206224] asyncMainTask---end---
2017-07-02 13:19:36.834 Test[7272:3206224] Task1---<NSThread: 0x608000072480>{number = 1, name = main}
2017-07-02 13:19:36.834 Test[7272:3206224] Task2---<NSThread: 0x608000072480>{number = 1, name = main}
2017-07-02 13:19:36.834 Test[7272:3206224] Task3---<NSThread: 0x608000072480>{number = 1, name = main}      

 結論:

   (1) 不開啟了新線程

   (2) 任務之間需要排隊,按照追加順序執行

6、主隊列 & 同步執行

- (void)syncMainTask{

    dispatch_queue_t queue = dispatch_get_main_queue();
 
    NSLog(@"syncMainTask---start---");

    dispatch_sync(queue, ^{
        NSLog(@"Task1---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task2---%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"Task3---%@", [NSThread currentThread]);
    });

    NSLog(@"syncMainTask---end---");
}      

 列印結果:

2017-07-02 13:22:31.860 Test[7335:3230988] syncMainTask---start---      

結論:

發生死鎖,程式崩潰。

好了GCD系列的上篇就寫到這裡,我将在後續系列中詳細介紹GCD的隊列系列和用法,以及使用GCD可能造成的問題及解決方案,水準有限,有不對的地方還望批評指正!

轉載于:https://www.cnblogs.com/beckwang0912/p/7100201.html