iOS進(jìn)階之多線程--GCD

GCD簡介

  • GCD全稱Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度系統(tǒng)”,是蘋果公司為多核的并行運(yùn)算提供的解決方案。
  • 開發(fā)者借助GCD無需直接操作線程,只需要將準(zhǔn)備好的和要執(zhí)行的任務(wù)添加到Dispatch Queue(隊(duì)列)中,GCD會(huì)根據(jù)隊(duì)列類型(串行&并發(fā))和任務(wù)的執(zhí)行類型(同步&異步)來確定要不要開啟子線程、和任務(wù)的執(zhí)行順序。
  • 任務(wù)的執(zhí)行順序遵循隊(duì)列的FIFO原則,先進(jìn)先出、后進(jìn)后出。
  • 并且要不要開啟線程、開幾條線程以及線程的生命周期(創(chuàng)建線程、調(diào)度熱舞、銷毀線程)都不需要開發(fā)者關(guān)心。
  • GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如:雙核、四核、乃至八核),本人也相信蘋果的GCD技術(shù)也是一大伏筆,雖然目前市場上的蘋果手機(jī)大都是雙核,相信在不久的未來蘋果推出更多核芯CPU時(shí)GCD會(huì)自動(dòng)適配,屆時(shí)所有程序員會(huì)發(fā)現(xiàn),蘋果已經(jīng)早先一步有了應(yīng)對(duì)多核硬件的技術(shù),并且也不用擔(dān)心硬件的革新帶來項(xiàng)目重構(gòu)的問題,所以放心地使用GCD!

GCD的幾個(gè)概念

  1. 任務(wù) 同步&異步:
 /**同步執(zhí)行*/
dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)
/**異步步執(zhí)行*/
dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)

這里只要執(zhí)行的代碼,為Block代碼塊。既然是Block,是不是煩惱的循環(huán)引用又來了,block只是一個(gè)局部變量,執(zhí)行完畢之后就釋放掉了,不用擔(dān)心循環(huán)引用問題。任務(wù)的執(zhí)行又分同步和異步。
同步:當(dāng)前任務(wù)(代碼)沒有執(zhí)行完畢不會(huì)執(zhí)行下一個(gè)任務(wù);
異步:當(dāng)前任務(wù)沒有執(zhí)行完畢同樣會(huì)執(zhí)行下一個(gè)任務(wù)(只要有任務(wù),GCD就回到線程池中取線程)(主隊(duì)列除外)

  1. 隊(duì)列 串行&并發(fā):
/**
參數(shù):
1.線程名稱
2.DISPATCH_QUEUE_SERIAL == NULL 串行
  DISPATCH_QUEUE_CONCURRENT  并發(fā)
*/
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr)

主要負(fù)責(zé)調(diào)度任務(wù),所有隊(duì)列都遵循FIFO原則。隊(duì)列又分為串行隊(duì)列和并發(fā)隊(duì)列。
串行 隊(duì)列:一個(gè)一個(gè)地調(diào)度任務(wù)。
并發(fā) 隊(duì)列:可以同時(shí)調(diào)度多個(gè)任務(wù),開不開線程是有任務(wù)決定的。如果是同步任務(wù):當(dāng)一個(gè)任務(wù)沒有執(zhí)行完成,隊(duì)列也會(huì)取任務(wù),只是取出的任務(wù)要等待前一個(gè)任務(wù)執(zhí)行完畢才開始執(zhí)行,不會(huì)開啟線程;如果是異步任務(wù):GCD會(huì)開啟線程同時(shí)執(zhí)行多個(gè)任務(wù)。

  1. 小結(jié):
    開不開線程取決于任務(wù),同步不開線程,異步開線程;
    開幾條線程由隊(duì)列決定,串行只開一條,并發(fā)(只有異步條件下)可以開啟多條。

GCD的相關(guān)用法

  1. 串行隊(duì)列,同步任務(wù)
    //串行
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", NULL);
    for (int i = 0; i < 10; i ++) {
        //同步
        dispatch_sync(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }

打印

2018-04-03 11:53:46.883277+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   0
2018-04-03 11:53:46.883576+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   1
2018-04-03 11:53:46.883763+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   2
2018-04-03 11:53:46.883928+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   3
2018-04-03 11:53:46.884098+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   4
2018-04-03 11:53:46.884260+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   5
2018-04-03 11:53:46.884390+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   6
2018-04-03 11:53:46.884529+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   7
2018-04-03 11:53:46.884648+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   8
2018-04-03 11:53:46.884789+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   9
  • 不會(huì)開啟線程,順序執(zhí)行
  1. 串行隊(duì)列,同步任務(wù)
  //串行
  dispatch_queue_t q = dispatch_queue_create("chen_jinguo",DISPATCH_QUEUE_CONCURRENT);
  for(int i = 0; i < 10; i ++){
     NSLog(@"---------%d-------",i);
    //異步
    dispatch_async(q, ^{
        NSLog(@"%@ %d",[NSThread currentThread],i);
    });
  }

打印

2018-04-03 11:58:17.697224+0800 GCD演示[1126:425688] ---------0-------
2018-04-03 11:58:17.697617+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   0
2018-04-03 11:58:17.697436+0800 GCD演示[1126:425688] ---------1-------
2018-04-03 11:58:17.698929+0800 GCD演示[1126:425688] ---------2-------
2018-04-03 11:58:17.699189+0800 GCD演示[1126:425688] ---------3-------
2018-04-03 11:58:17.699223+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   1
2018-04-03 11:58:17.699405+0800 GCD演示[1126:425688] ---------4-------
2018-04-03 11:58:17.699447+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   2
2018-04-03 11:58:17.701129+0800 GCD演示[1126:425688] ---------5-------
2018-04-03 11:58:17.702702+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   3
2018-04-03 11:58:17.703452+0800 GCD演示[1126:425688] ---------6-------
2018-04-03 11:58:17.703831+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   4
2018-04-03 11:58:17.704094+0800 GCD演示[1126:425688] ---------7-------
2018-04-03 11:58:17.704316+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   5
2018-04-03 11:58:17.743690+0800 GCD演示[1126:425688] ---------8-------
2018-04-03 11:58:17.743782+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   6
2018-04-03 11:58:17.743979+0800 GCD演示[1126:425688] ---------9-------
2018-04-03 11:58:17.744001+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   7
2018-04-03 11:58:17.744380+0800 GCD演示[1126:425688] come here
2018-04-03 11:58:17.744407+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   8
2018-04-03 11:58:17.744776+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{number = 3, name = (null)}   9
  • 會(huì)開啟一條線程,順序執(zhí)行
  1. 并發(fā)隊(duì)列,異步任務(wù)
    //隊(duì)列 - 并發(fā) DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", DISPATCH_QUEUE_CONCURRENT);
    //執(zhí)行 - 異步
    for (int i = 0; i < 10; i ++) {
        dispatch_async(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");

打印

2018-04-03 13:36:05.457846+0800 GCD演示[1213:499948] come here
2018-04-03 13:36:05.458007+0800 GCD演示[1213:500170] <NSThread: 0x604000471a80>{number = 4, name = (null)}   1
2018-04-03 13:36:05.458008+0800 GCD演示[1213:500169] <NSThread: 0x60000027ee40>{number = 3, name = (null)}   0
2018-04-03 13:36:05.458010+0800 GCD演示[1213:500171] <NSThread: 0x604000471300>{number = 6, name = (null)}   3
2018-04-03 13:36:05.458012+0800 GCD演示[1213:500180] <NSThread: 0x60000027eb80>{number = 5, name = (null)}   2
2018-04-03 13:36:05.458301+0800 GCD演示[1213:500170] <NSThread: 0x604000471a80>{number = 4, name = (null)}   5
2018-04-03 13:36:05.458310+0800 GCD演示[1213:500168] <NSThread: 0x60000027f200>{number = 7, name = (null)}   4
2018-04-03 13:36:05.458323+0800 GCD演示[1213:500169] <NSThread: 0x60000027ee40>{number = 3, name = (null)}   6
2018-04-03 13:36:05.458345+0800 GCD演示[1213:500171] <NSThread: 0x604000471300>{number = 6, name = (null)}   7
2018-04-03 13:36:05.458451+0800 GCD演示[1213:500168] <NSThread: 0x60000027f200>{number = 7, name = (null)}   8
2018-04-03 13:36:05.458476+0800 GCD演示[1213:500170] <NSThread: 0x604000471a80>{number = 4, name = (null)}   9
  • 會(huì)開啟多條線程,非順序執(zhí)行
  1. 并發(fā)隊(duì)列,同步任務(wù)
    //隊(duì)列 - 并發(fā) DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", DISPATCH_QUEUE_CONCURRENT);
    //執(zhí)行 - 同步
    for (int i = 0; i < 10; i ++) {
        dispatch_sync(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");

打印

2018-04-03 13:37:58.162147+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   0
2018-04-03 13:37:58.162436+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   1
2018-04-03 13:37:58.163300+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   2
2018-04-03 13:37:58.163621+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   3
2018-04-03 13:37:58.163761+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   4
2018-04-03 13:37:58.163916+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   5
2018-04-03 13:37:58.164035+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   6
2018-04-03 13:37:58.164410+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   7
2018-04-03 13:37:58.164839+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   8
2018-04-03 13:37:58.165329+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   9
2018-04-03 13:37:58.165824+0800 GCD演示[1228:510822] come here
  • 不會(huì)開啟線程,順序執(zhí)行
  1. 同步任務(wù)
    在開發(fā)中,通常會(huì)把耗時(shí)任務(wù)放在后臺(tái)執(zhí)行,有時(shí)候,有些任務(wù)彼此有“依賴”關(guān)系!
    例子:登錄、支付、下載
    利用同步任務(wù),能夠任務(wù)的依賴關(guān)系,前一個(gè)是同步任務(wù),如果不執(zhí)行完,隊(duì)列就不會(huì)調(diào)度后面的任務(wù)
dispatch_queue_t q = dispatch_queue_create("ChenJinguo", DISPATCH_QUEUE_CONCURRENT);
    //1、登錄
    dispatch_sync(q, ^{
        NSLog(@"用戶登錄----- %@",[NSThread currentThread]);
        
    });
    //2、支付
    dispatch_sync(q, ^{
        NSLog(@"用戶支付----- %@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10; i ++) {
        NSLog(@"%d\n",i);
    }

    //3、下載
    dispatch_async(q, ^{
        NSLog(@"用戶下載----- %@",[NSThread currentThread]);
    });
    NSLog(@"come here!");

打印

2018-04-03 13:43:05.538233+0800 GCD演示[1264:539460] 用戶登錄----- <NSThread: 0x604000072580>{number = 1, name = main}
2018-04-03 13:43:05.538480+0800 GCD演示[1264:539460] 用戶支付----- <NSThread: 0x604000072580>{number = 1, name = main}
2018-04-03 13:43:05.538938+0800 GCD演示[1264:539460] 0
2018-04-03 13:43:05.539076+0800 GCD演示[1264:539460] 1
2018-04-03 13:43:05.539220+0800 GCD演示[1264:539460] 2
2018-04-03 13:43:05.539321+0800 GCD演示[1264:539460] 3
2018-04-03 13:43:05.539431+0800 GCD演示[1264:539460] 4
2018-04-03 13:43:05.539679+0800 GCD演示[1264:539460] 5
2018-04-03 13:43:05.540171+0800 GCD演示[1264:539460] 6
2018-04-03 13:43:05.540635+0800 GCD演示[1264:539460] 7
2018-04-03 13:43:05.541084+0800 GCD演示[1264:539460] 8
2018-04-03 13:43:05.541530+0800 GCD演示[1264:539460] 9
2018-04-03 13:43:05.541870+0800 GCD演示[1264:539460] come here!
2018-04-03 13:43:05.541957+0800 GCD演示[1264:540171] 用戶下載----- <NSThread: 0x60400027d080>{number = 3, name = (null)}
  • 因?yàn)榈卿浐椭Ц抖际峭饺蝿?wù),在執(zhí)行完同步任務(wù)之后才會(huì)開啟子線程完成其他異步任務(wù)

思考:如果登錄、支付和下載都是耗時(shí)任務(wù),為了增強(qiáng)用戶體驗(yàn),不想將他們放在UI線程中,怎么樣完成這三個(gè)任務(wù)的同步呢?
方法:將三個(gè)任務(wù)作為一個(gè)異步任務(wù),這樣不管放在串行隊(duì)列或者并發(fā)隊(duì)列中,GCD都會(huì)開辟子線程調(diào)度此任務(wù),然后在開啟的子線程上同步調(diào)度三個(gè)任務(wù),就完成了異步中同步調(diào)度任務(wù)

  dispatch_queue_t q = dispatch_queue_create("chen", DISPATCH_QUEUE_CONCURRENT);
  //包裝三個(gè)任務(wù)為block  異步執(zhí)行
    void (^task)() = ^{
        dispatch_sync(q, ^{
            for (int i = 0; i < 10; i ++) {
                NSLog(@"%d--- %@\n",i,[NSThread currentThread]);
            }
        });

        //1、登錄
        dispatch_async(q, ^{
            NSLog(@"用戶登錄----- %@",[NSThread currentThread]);
            
        });
        //2、支付
        dispatch_async(q, ^{
            NSLog(@"用戶支付----- %@",[NSThread currentThread]);
        });

        
        //3、下載
        dispatch_async(q, ^{
            NSLog(@"用戶下載----- %@",[NSThread currentThread]);
        });
    };
    dispatch_async(q, task);

打印

2018-04-03 13:54:32.628778+0800 GCD演示[1297:579094] 0--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.628785+0800 GCD演示[1297:579087] 用戶登錄----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629000+0800 GCD演示[1297:579094] 1--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629150+0800 GCD演示[1297:579087] 用戶支付----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629169+0800 GCD演示[1297:579094] 2--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629267+0800 GCD演示[1297:579094] 3--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629269+0800 GCD演示[1297:579087] 用戶下載----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629835+0800 GCD演示[1297:579094] 4--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630147+0800 GCD演示[1297:579094] 5--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630346+0800 GCD演示[1297:579094] 6--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630643+0800 GCD演示[1297:579094] 7--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.631249+0800 GCD演示[1297:579094] 8--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.631599+0800 GCD演示[1297:579094] 9--- <NSThread: 0x604000273700>{number = 4, name = (null)}

這樣所有的任務(wù)都會(huì)在子線程中執(zhí)行,并且三個(gè)任務(wù)之間有依賴關(guān)系

  1. 全局隊(duì)列
    GCD提供默認(rèn)的并發(fā)隊(duì)列供全局使用:全局隊(duì)列。其獲取方式和參數(shù)如下:
dispatch_get_global_queue(long identifier, unsigned long flags);
  參數(shù)類型為:
    long identifier:ios 8.0 告訴隊(duì)列執(zhí)行任務(wù)的“服務(wù)質(zhì)量 quality of service”,系統(tǒng)提供的參數(shù)有:
     QOS_CLASS_USER_INTERACTIVE 0x21,              用戶交互(希望盡快完成,用戶對(duì)結(jié)果很期望,不要放太耗時(shí)操作)
     QOS_CLASS_USER_INITIATED 0x19,                用戶期望(不要放太耗時(shí)操作)
     QOS_CLASS_DEFAULT 0x15,                        默認(rèn)(不是給程序員使用的,用來重置對(duì)列使用的)
     QOS_CLASS_UTILITY 0x11,                        實(shí)用工具(耗時(shí)操作,可以使用這個(gè)選項(xiàng))
     QOS_CLASS_BACKGROUND 0x09,                     后臺(tái)
     QOS_CLASS_UNSPECIFIED 0x00,                    未指定
     iOS 7.0 之前 優(yōu)先級(jí)
     DISPATCH_QUEUE_PRIORITY_HIGH 2                 高優(yōu)先級(jí)
     DISPATCH_QUEUE_PRIORITY_DEFAULT 0              默認(rèn)優(yōu)先級(jí)
     DISPATCH_QUEUE_PRIORITY_LOW (-2)               低優(yōu)先級(jí)
     DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后臺(tái)優(yōu)先級(jí)

    BACKGROUND表示用戶不需要知道任務(wù)什么時(shí)候完成,如果選擇這個(gè)選項(xiàng)速度慢得令人發(fā)指,非常不利于調(diào)試!對(duì)于優(yōu)先級(jí)推薦不要搞得太負(fù)責(zé),就用最簡單,以免發(fā)生優(yōu)先級(jí)反轉(zhuǎn)。
     
    unsigned long flags:蘋果官方文檔是這樣解釋的: Flags that are reserved for future use。標(biāo)記是為了未來使用保留的!所以這個(gè)參數(shù)應(yīng)該永遠(yuǎn)指定為0

注意:全局隊(duì)列屬于并發(fā)隊(duì)列,建議在企業(yè)級(jí)應(yīng)用開發(fā)過程中如果不使用全局隊(duì)列,盡量給隊(duì)列起名,這樣有利于錯(cuò)誤跟蹤;另外,在MRC模式下,隊(duì)列需要releasedispatch_release(q);//ARC 情況下不需要release!

  1. 延時(shí)執(zhí)行dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
    /**參數(shù)
     1、when 時(shí)間
     2、queue
     3、Block
     */
    //從現(xiàn)在開始執(zhí)行多少納秒之后,讓queue調(diào)度Block的任務(wù)并且異步執(zhí)行
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
    dispatch_after(when, dispatch_queue_create("jinguo", NULL), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
  1. 執(zhí)行一次dispatch_once(dispatch_once_t * _Nonnull predicate, ^(void)block) ;
    執(zhí)行一次經(jīng)常在單例中用到,是蘋果提供的一次性機(jī)制,不僅能保證代碼只執(zhí)行一次,并且線程安全,線程安全表現(xiàn)在:dispatch_once_t * _Nonnull predicate參數(shù),其原理官方頭文件也有表現(xiàn):
_dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    } else {
        dispatch_compiler_barrier();
    }
    DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

其實(shí)具體是什么原理我也看不懂,畢竟C語言認(rèn)識(shí)我我不認(rèn)識(shí)它,其大概意思是我們外面定義的dispatch_once_t參數(shù)在傳入這個(gè)函數(shù)之后,使用它來保證線程安全的,其實(shí)我們做一次打印也可以看出個(gè)大概

   for (int i = 0; i < 10; i ++) {
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           static dispatch_once_t onceToken;
           NSLog(@"%ld",onceToken);
           dispatch_once(&onceToken, ^{
               NSLog(@" %@come here!",[NSThread currentThread]);
           });
           NSLog(@"來了 ----%d %@",i,[NSThread currentThread]);
       });
   }

打印

2018-04-03 14:17:22.679810+0800 GCD演示[1372:683676] 0
2018-04-03 14:17:22.679816+0800 GCD演示[1372:683678] 0
2018-04-03 14:17:22.679810+0800 GCD演示[1372:682541] 0
2018-04-03 14:17:22.679866+0800 GCD演示[1372:683679] 0
2018-04-03 14:17:22.680138+0800 GCD演示[1372:683678]  <NSThread: 0x60000026f180>{number = 3, name = (null)}come here!
2018-04-03 14:17:22.680170+0800 GCD演示[1372:683680] 5385
2018-04-03 14:17:22.680480+0800 GCD演示[1372:683681] 5385
2018-04-03 14:17:22.680785+0800 GCD演示[1372:683676] 來了 ----1 <NSThread: 0x604000470480>{number = 6, name = (null)}
2018-04-03 14:17:22.680785+0800 GCD演示[1372:682541] 來了 ----0 <NSThread: 0x60000026ec00>{number = 5, name = (null)}
2018-04-03 14:17:22.680795+0800 GCD演示[1372:683678] 來了 ----2 <NSThread: 0x60000026f180>{number = 3, name = (null)}
2018-04-03 14:17:22.680805+0800 GCD演示[1372:683680] 來了 ----4 <NSThread: 0x604000470a40>{number = 4, name = (null)}
2018-04-03 14:17:22.680813+0800 GCD演示[1372:683679] 來了 ----3 <NSThread: 0x604000470800>{number = 7, name = (null)}
2018-04-03 14:17:22.680992+0800 GCD演示[1372:683681] 來了 ----5 <NSThread: 0x600000268680>{number = 8, name = (null)}
2018-04-03 14:17:22.681220+0800 GCD演示[1372:683682] -1
2018-04-03 14:17:22.681490+0800 GCD演示[1372:683683] -1
2018-04-03 14:17:22.681692+0800 GCD演示[1372:683676] -1
2018-04-03 14:17:22.681766+0800 GCD演示[1372:683684] -1
2018-04-03 14:17:22.683852+0800 GCD演示[1372:683682] 來了 ----6 <NSThread: 0x60000026fa80>{number = 9, name = (null)}
2018-04-03 14:17:22.684087+0800 GCD演示[1372:683683] 來了 ----7 <NSThread: 0x60000026f980>{number = 10, name = (null)}
2018-04-03 14:17:22.684468+0800 GCD演示[1372:683676] 來了 ----8 <NSThread: 0x604000470480>{number = 6, name = (null)}
2018-04-03 14:17:22.685031+0800 GCD演示[1372:683684] 來了 ----9 <NSThread: 0x6040004711c0>{number = 11, name = (null)}

onceToken的值一開始是0,執(zhí)行到一次性語句時(shí)變?yōu)?385(或許是其他什么數(shù)),執(zhí)行完畢變?yōu)?1,什么原理還望大神的指點(diǎn)。
還有一種一次性執(zhí)行的方式:互斥鎖 @synchronized(),在單例中用到過,下面將兩種單例創(chuàng)建的方式列舉一下,并比較兩種方式創(chuàng)建單例的效率如何:

@synchronized():

#import "Singleton.h"
static Singleton *_instance;
@implementation Singleton
+ (instancetype)share{
    return [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    @synchronized(self){
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}
@end

onceToken:

#import "Singleton2.h"
static Singleton2 *_instance;

@implementation Singleton2
+ (instancetype)share{
    return [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    });
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}
@end

蘋果推薦使用GCD的一次性,效率高,而互斥鎖效率低,下面比較一下兩種方式的執(zhí)行時(shí)間就一目了然了:

    //@synchronized
    //獲取代碼開始執(zhí)行時(shí)時(shí)間
    CFAbsoluteTime synBegin =CFAbsoluteTimeGetCurrent();
    //獲取代碼結(jié)束執(zhí)行時(shí)時(shí)間
    Singleton *synSingle = [Singleton share];
    CFAbsoluteTime synEnd =CFAbsoluteTimeGetCurrent();
    //計(jì)算開始和結(jié)束的時(shí)間差,該時(shí)間差就是循環(huán)創(chuàng)建單例需要的時(shí)間
    NSLog(@"synchronized------%f",synBegin- synEnd);
    
    //onceToken
    //獲取代碼開始執(zhí)行時(shí)時(shí)間
    CFAbsoluteTime onceBegin =CFAbsoluteTimeGetCurrent();
    //獲取代碼結(jié)束執(zhí)行時(shí)時(shí)間
    Singleton2 *onceSingle = [Singleton share];
    CFAbsoluteTime onceEnd =CFAbsoluteTimeGetCurrent();
    //計(jì)算開始和結(jié)束的時(shí)間差,該時(shí)間差就是循環(huán)創(chuàng)建單例需要的時(shí)間
    NSLog(@"onceToken---------%f",onceBegin- onceEnd);

打印結(jié)果

2018-04-03 15:18:01.046723+0800 GCD演示[1580:939403] synchronized-------0.000021
2018-04-03 15:18:01.046944+0800 GCD演示[1580:939403] onceToken----------0.000004
  1. 調(diào)度組 dispatch_group_t
    GCD頭文件group.h中談到,可以將一組block提交到調(diào)度組(dispatch_group)中,執(zhí)行逐個(gè)串行回調(diào),下面來看看相關(guān)函數(shù)。
  • dispatch_group_t dispatch_group_create(void);
    創(chuàng)建一個(gè)調(diào)度組,釋放調(diào)度組使用dispatch_release()函數(shù),創(chuàng)建成功返回一個(gè)dispatch_group調(diào)度組,失敗則返回NULL.

  • void dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
    提交一個(gè)閉包函數(shù)(block)到queue中,并關(guān)聯(lián)到指定的group調(diào)度組.通過typedef void (^dispatch_block_t)(void);我們可以發(fā)現(xiàn),該函數(shù)無法給block傳遞參數(shù).
    1.group 指定的調(diào)度組,block的關(guān)聯(lián)調(diào)度組。
    2.queue 提交閉包函數(shù)(block)的隊(duì)列。
    3.block 提交到指定queue的閉包函數(shù)block。

  • void dispatch_group_async_f(dispatch_group_t group,dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
    提交一個(gè)函數(shù)指針(dispatch_function_t)到queue中,并關(guān)聯(lián)到指定的group調(diào)度組,函數(shù)返回void.
    1.group 指定的調(diào)度組,block的關(guān)聯(lián)調(diào)度組。
    2.queue 提交閉包函數(shù)(block)的隊(duì)列。
    3.context 傳遞到函數(shù)中的的參數(shù)。
    4.work 在指定的queue中的指定函數(shù)。

  • long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
    執(zhí)行等待,等待所有關(guān)聯(lián)到group調(diào)度組的block執(zhí)行完成,或者等待timeout發(fā)生超時(shí),當(dāng)在超時(shí)時(shí)間timeout內(nèi)執(zhí)行完了所有的block函數(shù),則返回0,否則返回非0值。
    1.group 給定調(diào)度組
    2.timeout 如果group調(diào)度組里邊的block執(zhí)行時(shí)間非常長,函數(shù)的等待時(shí)間.

  • void dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
    該函數(shù)指定了一個(gè)block,當(dāng)group調(diào)度組里邊的所有block都執(zhí)行完成時(shí),將通知block關(guān)聯(lián)到group中,并加入到給定的queue隊(duì)列里,當(dāng)group調(diào)度組當(dāng)前沒有任何block關(guān)聯(lián)的時(shí)候?qū)⒘⒓磳lock提交到queue隊(duì)列,并與group調(diào)度組關(guān)聯(lián),該函數(shù)返回void.
    1.group 給定的調(diào)度組
    2.queue 給定的隊(duì)列.
    3.給定的閉包函數(shù).

  • void dispatch_group_notify_f(dispatch_group_t group,dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
    與disptch_group_notify類似,提交的一個(gè)函數(shù)work作為執(zhí)行體,context是執(zhí)行時(shí)傳遞的參數(shù),該函數(shù)返回void.

  • void dispatch_group_enter(dispatch_group_t group);

  • void dispatch_group_leave(dispatch_group_t group);
    這一對(duì)函數(shù)調(diào)用一次意味著非使用dispatch_group_async方式,將一個(gè)block提交到指定的queue上并關(guān)聯(lián)到group調(diào)度組.兩個(gè)函數(shù)必須成對(duì)出現(xiàn)。

在實(shí)際開發(fā)中,需要開啟N個(gè)異步線程,但是后續(xù)操作,需要依賴N個(gè)線程返回的數(shù)據(jù),需要接收所有線程任務(wù)執(zhí)行完成的通知。

    //1.對(duì)列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //2.調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    //3.添加任務(wù),讓隊(duì)列調(diào)度,任務(wù)執(zhí)行情況,最后通知群組
    dispatch_group_async(group, q, ^{
        NSLog(@"Download A%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        sleep(1.0);
        NSLog(@"Download B%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"Download C%@",[NSThread currentThread]);
    });
    //所有任務(wù)完成之后通知群組
    //用調(diào)度組,可以監(jiān)聽全局隊(duì)列的任務(wù),主隊(duì)列去執(zhí)行最后的任務(wù)
    //dispatch_group_notify 本身也是異步執(zhí)行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@" OK %@",[NSThread currentThread]);
    });
    NSLog(@"come here");

注:dispatch_group_notify這個(gè)函數(shù)是異步的,如果要換成同步用dispatch_group_wait(group, DISPATCH_TIME_FOREVER).群組不空,這句代碼一直等,下面代碼不執(zhí)行

// 隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 調(diào)度組
    dispatch_group_t group = dispatch_group_create();
    
    // 1. 進(jìn)入群組,給 group 打一個(gè)標(biāo)記,在后續(xù)緊接著的 block 歸 group 監(jiān)聽
    // dispatch_group_enter 和 dispatch_group_leave 必須成對(duì)出現(xiàn)!
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:10];
        NSLog(@"download A - %@", [NSThread currentThread]);
        // 耗時(shí)操作代碼

        // 2. 離開群組
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download B - %@", [NSThread currentThread]);
        // 耗時(shí)操作代碼

        // 2. 離開群組
        dispatch_group_leave(group);
    });
    
    // 等待群組空,一直到永遠(yuǎn),群組不空,這句代碼就死等,同步
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"OK");
  1. 主隊(duì)列 dispatch_get_main_queue()
    主隊(duì)列是用來在主線程上調(diào)度任務(wù),會(huì)在程序開始時(shí)創(chuàng)建,只需要獲取。
    異步任務(wù)
    //主隊(duì)列 --> 已啟動(dòng)主線程就可以拿到主隊(duì)列
    dispatch_queue_t q = dispatch_get_main_queue();
    //異步任務(wù)
    dispatch_async(q, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10 ; i ++) {
        NSLog(@"come here");
    }

同步任務(wù)會(huì)造成死鎖

    //崩潰!?。。。?!
    dispatch_sync(q, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10 ; i ++) {
        NSLog(@"come here");
    }

解決辦法

    //主隊(duì)列同步任務(wù)(不死鎖)
    void (^task)() = ^{
        dispatch_queue_t q = dispatch_get_main_queue();
        //2.異步任務(wù)
        dispatch_sync(q, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        for (int i = 0; i < 10 ; i ++) {
            NSLog(@"come here %@",[NSThread currentThread]);
        }
    };
    dispatch_async(dispatch_get_global_queue(0, 0), task);
更新中。。。。。。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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