***線程,GCD,runloop(1)

文章主要從幾個方面來整理

1、線程介紹
2、GCD的使用
3、多線程編程的多種方式
4、線程同步和線程安全
5、runloop如何進(jìn)行線程間的交互
6、一些常用知識點

第一篇:線程

1.1 什么是進(jìn)程

進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序
每個進(jìn)程之間是獨立的,每個進(jìn)程均運行在其專用且受保護(hù)的內(nèi)存空間內(nèi)
241224432836121.png

比如同時打開QQ、Xcode,系統(tǒng)就會分別啟動2個進(jìn)程
通過“活動監(jiān)視器”可以查看Mac系統(tǒng)中所開啟的進(jìn)程

1.2 什么是線程

 1個進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個進(jìn)程至少要有1條線程)
線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行
比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行
241225080809896.png

1.3 線程的串行

1個線程中任務(wù)的執(zhí)行是串行的
如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù)
也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)
比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)

1.4 多線程

1.4.1 什么是多線程

      1個進(jìn)程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù)
      進(jìn)程 ->車間,線程->車間工人
      多線程技術(shù)可以提高程序的執(zhí)行效率
      比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C)

1.4.2 多線程的原理

      同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
      多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)
      如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
      思考:如果線程非常非常多,會發(fā)生什么情況?
      CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源
      每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)

1.4.3 多線程的優(yōu)缺點

      多線程的優(yōu)點
            能適當(dāng)提高程序的執(zhí)行效率
            能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
      多線程的缺點
            開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用                
            512KB),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
            線程越多,CPU在調(diào)度線程上的開銷就越大
            程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享  

1.4.4 多線程在iOS開發(fā)中的應(yīng)用

      主線程:一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為“主線程”或“UI線程”
      主線程的主要作用
              顯示\刷新UI界面
              處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
      ios中為什么UI操作要在主線程?
      因為iOS中UI組件非線程安全的,如果放在輔助線程中操作必然可能導(dǎo)致線程之間        
      的競爭,會導(dǎo)致問題。
      主線程的使用注意:別將比較耗時的操作放到主線程中。
      耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗

第二篇:GCD的使用

2.1 GCD簡介

2.1.1 什么是GCD呢?

      Grand Central Dispatch (GCD) 是Apple開發(fā)的一個多核編程的較新的解決方法。                
      它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng)。它是一個在 
      線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù)。在Mac OS X 10.6雪豹中首次推出,也可以
      IOS 4及以上版本使用。

2.1.2 GCD的優(yōu)點

  • GCD可用于多核的并行運算
  • GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
  • GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
  • 程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼

2.2 任務(wù)和隊列

任務(wù)就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼。在GCD中是放在block中的。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行和異步執(zhí)行。兩者的主要區(qū)別是:是否具備開啟新線程的能力。

  • 同步執(zhí)行(sync):只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
  • 異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

隊列這里的隊列指任務(wù)隊列,即用來存放任務(wù)的隊列。隊列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。在GCD中有兩種隊列:串行隊列和并發(fā)隊列。

  • 并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
    并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
  • 串行隊列(Serial Dispatch Queue):讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))

2.3 GCD使用步驟

GCD的使用步驟其實很簡單,只有兩步。

創(chuàng)建一個隊列(串行隊列或并發(fā)隊列)
將任務(wù)添加到隊列中,然后系統(tǒng)就會根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)
下邊來看看隊列的創(chuàng)建方法和任務(wù)的創(chuàng)建方法。

2.3.1 隊列的創(chuàng)建

  • 可以使用dispatch_queue_create來創(chuàng)建對象,需要傳入兩個參數(shù),第一個參數(shù)表示隊列的唯一標(biāo)識符,用于DEBUG,可為空;第二個參數(shù)用來識別是串行隊列還是并發(fā)隊列。DISPATCH_QUEUE_SERIAL表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并發(fā)隊列。
// 串行隊列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
  • 對于并發(fā)隊列,還可以使用dispatch_get_global_queue來創(chuàng)建全局并發(fā)隊列。GCD默認(rèn)提供了全局的并發(fā)隊列,需要傳入兩個參數(shù)。第一個參數(shù)表示隊列優(yōu)先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個參數(shù)暫時沒用,用0即可。

2.3.2 任務(wù)的創(chuàng)建方法

// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});

雖然使用GCD只需兩步,但是既然我們有兩種隊列,兩種任務(wù)執(zhí)行方式,那么我們就有了四種不同的組合方式。這四種不同的組合方式是

  • 1 并發(fā)隊列 + 同步執(zhí)行
  • 2 并發(fā)隊列 + 異步執(zhí)行
  • 3 串行隊列 + 同步執(zhí)行
  • 4 串行隊列 + 異步執(zhí)行
    實際上,我們還有一種特殊隊列是主隊列,那樣就有六種不同的組合方式了。
  • 1 主隊列 + 同步執(zhí)行
  • 2 主隊列 + 異步執(zhí)行

那么這幾種不同組合方式各有什么區(qū)別呢,這里為了方便,先上結(jié)果,再來講解。為圖省事,直接查看表格結(jié)果


屏幕快照 2017-07-06 下午1.05.43.png

2.4 GCD的使用

并發(fā)隊列的兩種使用方法

2.4.1 并發(fā)隊列 + 同步執(zhí)行

  • 不會開啟新線程,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)
- (void) syncConcurrent
{
    NSLog(@"syncConcurrent---begin");
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent---end");
}

輸出結(jié)果:

2016-09-03 19:22:27.577 GCD[11557:1897538] syncConcurrent---begin
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 2------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 2------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] syncConcurrent---end
  • 從并發(fā)隊列 + 同步執(zhí)行中可以看到,所有任務(wù)都是在主線程中執(zhí)行的。由于只有一個線程,所以任務(wù)只能一個一個執(zhí)行。
  • 同時我們還可以看到,所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務(wù)是添加到隊列中馬上執(zhí)行的。

2.4.2 并發(fā)隊列 + 異步執(zhí)行

  • 可同時開啟多線程,任務(wù)交替執(zhí)行
- (void) asyncConcurrent
{
    NSLog(@"asyncConcurrent---begin");

    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

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

輸出結(jié)果

    2016-09-03 19:27:31.503 GCD[11595:1901548] asyncConcurrent---begin
    2016-09-03 19:27:31.504 GCD[11595:1901548] asyncConcurrent---end
    2016-09-03 19:27:31.504 GCD[11595:1901626] 1------<NSThread:     0x7f8309c22080>{number = 2, name = (null)}
    2016-09-03 19:27:31.504 GCD[11595:1901625] 2------<NSThread:     0x7f8309f0b790>{number = 4, name = (null)}
    2016-09-03 19:27:31.504 GCD[11595:1901855] 3------<NSThread: 0x7f8309e1a950>{number = 3, name = (null)}
    2016-09-03 19:27:31.504 GCD[11595:1901626] 1------<NSThread: 0x7f8309c22080>{number = 2, name = (null)}
    2016-09-03 19:27:31.504 GCD[11595:1901625] 2------<NSThread: 0x7f8309f0b790>{number = 4, name = (null)}
    2016-09-03 19:27:31.505 GCD[11595:1901855] 3------<NSThread: 0x7f8309e1a950>{number = 3, name = (null)}
  • 在并發(fā)隊列 + 異步執(zhí)行中可以看出,除了主線程,又開啟了3個線程,并且任務(wù)是交替著同時執(zhí)行的。
  • 另一方面可以看出,所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的。說明任務(wù)不是馬上執(zhí)行,而是將所有任務(wù)添加到隊列之后才開始異步執(zhí)行。

2.4.3 串行隊列 + 同步執(zhí)行

  • 不會開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)
 (void) syncSerial
{
    NSLog(@"syncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"syncSerial---end");
}
輸出結(jié)果為:
2016-09-03 19:29:00.066 GCD[11622:1903904] syncSerial---begin
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 3------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] 3------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] syncSerial---end
  • 在串行隊列 + 同步執(zhí)行可以看到,所有任務(wù)都是在主線程中執(zhí)行的,并沒有開啟新的線程。而且由于串行隊列,所以按順序一個一個執(zhí)行。
  • 同時我們還可以看到,所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務(wù)是添加到隊列中馬上執(zhí)行的。

2.4.4 串行隊列 + 異步執(zhí)行

  • 會開啟新線程,但是因為任務(wù)是串行的,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)
 (void) asyncSerial
{
    NSLog(@"asyncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"asyncSerial---end");
}
輸出結(jié)果為:
2016-09-03 19:30:08.363 GCD[11648:1905817] asyncSerial---begin
2016-09-03 19:30:08.364 GCD[11648:1905817] asyncSerial---end
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
  • 在串行隊列 + 異步執(zhí)行可以看到,開啟了一條新線程,但是任務(wù)還是串行,所以任務(wù)是一個一個執(zhí)行。
  • 另一方面可以看出,所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的。說明任務(wù)不是馬上執(zhí)行,而是將所有任務(wù)添加到隊列之后才開始同步執(zhí)行。

2.4.5 主隊列 + 同步執(zhí)行

  • 互等卡住不可行(在主線程中調(diào)用)
- (void)syncMain
{
    NSLog(@"syncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });   

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

輸出結(jié)果
2016-09-03 19:32:15.356 GCD[11670:1908306] syncMain---begin

這時候,我們驚奇的發(fā)現(xiàn),在主線程中使用主隊列 + 同步執(zhí)行,任務(wù)不再執(zhí)行了,而且syncMain---end也沒有打印。這是為什么呢?

這是因為我們在主線程中執(zhí)行這段代碼。我們把任務(wù)放到了主隊列中,也就是放到了主線程的隊列中。而同步執(zhí)行有個特點,就是對于任務(wù)是立馬執(zhí)行的。那么當(dāng)我們把第一個任務(wù)放進(jìn)主隊列中,它就會立馬執(zhí)行。但是主線程現(xiàn)在正在處理syncMain方法,所以任務(wù)需要等syncMain執(zhí)行完才能執(zhí)行。而syncMain執(zhí)行到第一個任務(wù)的時候,又要等第一個任務(wù)執(zhí)行完才能往下執(zhí)行第二個和第三個任務(wù)。

那么,現(xiàn)在的情況就是syncMain方法和第一個任務(wù)都在等對方執(zhí)行完畢。這樣大家互相等待,所以就卡住了,所以我們的任務(wù)執(zhí)行不了,而且syncMain---end也沒有打印。

要是如果不再主線程中調(diào)用,而在其他線程中調(diào)用會如何呢?

  • 不會開啟新線程,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)(在其他線程中調(diào)用)
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    [self syncMain];
});
輸出結(jié)果:
2016-09-03 19:32:45.496 GCD[11686:1909617] syncMain---begin
2016-09-03 19:32:45.497 GCD[11686:1909374] 1------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 1------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 2------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 2------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909374] 3------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909374] 3------<NSThread: 0x7faef2f01600>{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909617] syncMain---end
  • 在其他線程中使用主隊列 + 同步執(zhí)行可看到:所有任務(wù)都是在主線程中執(zhí)行的,并沒有開啟新的線程。而且由于主隊列是串行隊列,所以按順序一個一個執(zhí)行。
  • 同時我們還可以看到,所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務(wù)是添加到隊列中馬上執(zhí)行的。

2.4.6 主隊列 + 異步執(zhí)行

  • 只在主線程中執(zhí)行任務(wù),執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)
- (void)asyncMain
{
    NSLog(@"asyncMain---begin");

    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });  

    NSLog(@"asyncMain---end");
}
輸出結(jié)果:
2016-09-03 19:33:54.995 GCD[11706:1911313] asyncMain---begin
2016-09-03 19:33:54.996 GCD[11706:1911313] asyncMain---end
2016-09-03 19:33:54.996 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
  • 我們發(fā)現(xiàn)所有任務(wù)都在主線程中,雖然是異步執(zhí)行,具備開啟線程的能力,但因為是主隊列,所以所有任務(wù)都在主線程中,并且一個接一個執(zhí)行。
  • 另一方面可以看出,所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的。說明任務(wù)不是馬上執(zhí)行,而是將所有任務(wù)添加到隊列之后才開始同步執(zhí)行。
    弄懂了難理解、繞來繞去的隊列+任務(wù)之后,我們來學(xué)習(xí)一個簡單的東西——GCD線程之間的通訊。

2.4.7 GCD線程之間的通訊

在iOS開發(fā)過程中,我們一般在主線程里邊進(jìn)行UI刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當(dāng)我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
    }

    // 回到主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",[NSThread currentThread]);
    });
});
輸出結(jié)果:
2016-09-03 19:34:59.165 GCD[11728:1913039] 1------<NSThread: 0x7f8319c06820>{number = 2, name = (null)}
2016-09-03 19:34:59.166 GCD[11728:1913039] 1------<NSThread: 0x7f8319c06820>{number = 2, name = (null)}
2016-09-03 19:34:59.166 GCD[11728:1912961] 2-------<NSThread: 0x7f8319e00560>{number = 1, name = main}
  • 可以看到在其他線程中先執(zhí)行操作,執(zhí)行完了之后回到主線程執(zhí)行主線程的相應(yīng)操作

2.4.7 GCD的其他方法

  • 1 GCD的柵欄方法 dispatch_barrier_async
    我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作。這樣我們就需要一個相當(dāng)于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來,當(dāng)然這里的操作組里可以包含一個或多個任務(wù)。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。
- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}
輸出結(jié)果:
2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}

可以看出在執(zhí)行完柵欄前面的操作之后,才執(zhí)行柵欄操作,最后再執(zhí)行柵欄后邊的操作。

  • 2 GCD的延時執(zhí)行方法 dispatch_after
    當(dāng)我們需要延遲執(zhí)行一段代碼時,就需要用到GCD的dispatch_after方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
   NSLog(@"run-----");
});
  • 3 GCD的一次性代碼(只執(zhí)行一次) dispatch_once
    我們在創(chuàng)建單例、或者有整個程序運行過程中只執(zhí)行一次的代碼時,我們就用到了GCD的dispatch_once方法。使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次。防止多線程訪問時多次執(zhí)行。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
  • 4 GCD的快速迭代方法 dispatch_apply
    通常我們會用for循環(huán)遍歷,但是GCD給我們提供了快速迭代的方法dispatch_apply,使我們可以同時遍歷。比如說遍歷0~5這6個數(shù)字,for循環(huán)的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數(shù)字。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});
輸出結(jié)果:
2016-09-03 19:37:02.250 GCD[11764:1915764] 1------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915885] 0------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}
2016-09-03 19:37:02.250 GCD[11764:1915886] 2------<NSThread: 0x7fac9a542b20>{number = 3, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915764] 4------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915884] 3------<NSThread: 0x7fac9a76ca10>{number = 4, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915885] 5------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}

從輸出結(jié)果中前邊的時間中可以看出,幾乎是同時遍歷的。

  • 5 GCD的隊列組 dispatch_group
    有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時操作,然后當(dāng)2個耗時操作都執(zhí)行完畢后再回到主線程執(zhí)行操作。這時候我們可以用到GCD的隊列組。
    • a 我們可以先把任務(wù)放到隊列中,然后將隊列放入隊列組中。
    • b 調(diào)用隊列組的dispatch_group_notify回到主線程執(zhí)行操作。
dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執(zhí)行1個耗時的異步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執(zhí)行1個耗時的異步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的異步操作都執(zhí)行完畢后,回到主線程...
});

第三篇:多線程編程的多種方式-----轉(zhuǎn)下篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容