iOS 多線程之GCD

1 GCD簡述

Apple源碼--Dispatch

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

GCD優(yōu)點

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

2 GCD任務和隊列

任務

任務: 執(zhí)行操作的意思,就是說你在線程中執(zhí)行的那段代碼.在GCD中是放在block中的.執(zhí)行任務有兩種方式同步執(zhí)行異步執(zhí)行

  • 同步執(zhí)行(sync):
    (1) 同步添加任務到指定的隊列中,在添加的任務執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務完成之后再繼續(xù)執(zhí)行
    (2) 能在當前線程中執(zhí)行任務,不具備開啟新線程的能力
  • 異步執(zhí)行(async):
    (1) 異步添加任務到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務
    (2) 可以在新的線程中執(zhí)行任務,具備開啟新線程的能力
    注意: <u>異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程.這跟任務所指定的隊列類型有關(下面會講)</u>

任務的創(chuàng)建分為:同步任務dispatch_sync和異步任務dispatch_async

dispatch_sync(queue, ^{
    // 同步執(zhí)行任務
    // code snippet
});
dispatch_async(queue, ^{
    // 異步執(zhí)行任務
    // code snippet
});

隊列(Dispatch Queue)

隊列:隊列指執(zhí)行任務的等待隊列,即用來存放任務的隊列.隊列是一種特殊的線性表,采用FIFIO(先進先出)的原則.

image

GCD隊列分為兩種:串行隊列并行隊列
主要區(qū)別: 執(zhí)行順序不同,以及開啟線程數(shù)不同.

  • 串行隊列(Serial Dispatch Queue):每次只有一個任務被執(zhí)行.讓任務一個接著一個地執(zhí)行.(只開啟一個線程,一個任務執(zhí)行完畢后,再執(zhí)行下一個任務)
  • 并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務并發(fā)(同時)執(zhí)行.(可以開啟多個線程,并且同時執(zhí)行任務)

注意:<u>并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效.其他線程下,串行執(zhí)行任務</u>

image

隊列的創(chuàng)建:dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

  • 參數(shù)0: 隊列的唯一標識符,隊列的名稱推薦使用應用程序id這種逆序全程域名
  • 參數(shù)1: 用來識別是串行隊列還是并發(fā)隊列 (DISPATCH_QUEUE_SERIAL, DISPATCH_QUEUE_CONCURRENT)
// 串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列
dispatch_queue_t concurrentlQueue = dispatch_queue_create("com.appleid.functionB", DISPATCH_QUEUE_CONCURRENT);
// 主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 全局并發(fā)隊列 (參數(shù)0: 填寫默認 , 參數(shù)1: 填寫0)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3 隊列和同步,異步任務組合

區(qū)別 并發(fā)隊列 串行隊列 主隊列
同步(sync) 沒有開啟新線程,串行執(zhí)行任務 沒有開啟新線程,串行執(zhí)行任務 死鎖卡住不執(zhí)行
異步(async) 有開啟新線程,并發(fā)執(zhí)行任務 有開啟新線程(1條),串行執(zhí)行任務 沒有開啟新線程,串行執(zhí)行任務

4 GCD方法

4.1 dispatch_after:延時執(zhí)行方法

主要:<u>dispatch_after方法并不是在指定時間之后才開始執(zhí)行處理,而是在指定時間之后將任務追加到主隊列中.準確來說,這個時間并不是絕對準確的,但想要大致延遲執(zhí)行任務,dispatch_after 方法是很有效的</u>

- (void)after {
    NSLog(@"當前線程%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"線程after:%@", [NSThread currentThread]);  // 打印當前線程
    });
}

4.2 dispatch_once:只執(zhí)行一次

在創(chuàng)建單例、或者有整個程序運行過程中只執(zhí)行一次的代碼時,就可以使用dispatch_once方法.dispatch_once方法能保證某段代碼在程序運行過程中只被執(zhí)行1次,并且即使在多線程的環(huán)境下, dispatch_once也可以保證線程安全.

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執(zhí)行一次, 默認線程安全
        // code snippet
    });
}

4.3 dispatch_barrier_async: 柵欄函數(shù)

Apple官方文檔

對這個函數(shù)的調(diào)用總是在block被提交之后立即返回,并且從不等block待被調(diào)用.當barrier block到達私有并發(fā)隊列的前端時,它不會立即執(zhí)行.相反,隊列將等待,直到當前執(zhí)行的塊完成執(zhí)行.此時,barrier block自己執(zhí)行.在barrier block之后提交的任何block都不會執(zhí)行,直到barrier block完成.
您指定的隊列應該是您自己使用dispatch_queue_create函數(shù)創(chuàng)建的并發(fā)隊列.如果傳遞給此函數(shù)的隊列是一個串行隊列或一個全局并發(fā)隊列,則此函數(shù)的行為與dispatch_async函數(shù)類似.

image
  • dispatch_barrier_sync

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務3, %@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務3, %@", [NSThread currentThread]);
    });

    
    dispatch_barrier_sync(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務6, %@", [NSThread currentThread]);
    });
    
    NSLog(@"任務7, %@", [NSThread currentThread]);
}];
        NSLog(@"barrier任務4, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務6, %@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

任務2, <NSThread: 0x139040570>{number = 4, name = (null)}
任務3, <NSThread: 0x139458a90>{number = 6, name = (null)}
任務1, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務4 barrier, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務7, <NSThread: 0x137e0b8d0>{number = 1, name = main}
任務5, <NSThread: 0x139043ce0>{number = 5, name = (null)}
任務6, <NSThread: 0x139458a90>{number = 6, name = (null)}
  • dispatch_barrier_async
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("com.appleid.functionA", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任務2, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務3, %@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務4 barrier, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務5, %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務6, %@", [NSThread currentThread]);
    });

    NSLog(@"任務7, %@", [NSThread currentThread]);
}

執(zhí)行結(jié)果:

任務7, <NSThread: 0x10360aea0>{number = 1, name = main}
任務2, <NSThread: 0x1035a4f90>{number = 3, name = (null)}
任務1, <NSThread: 0x105e79130>{number = 6, name = (null)}
任務3, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務4 barrier, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務5, <NSThread: 0x1036afae0>{number = 5, name = (null)}
任務6, <NSThread: 0x105e79130>{number = 6, name = (null)}

4.4 dispatch_apply:快速迭代(高效for循環(huán))

Apple官方文檔

此函數(shù)將多個調(diào)用的block提交給調(diào)度隊列,并等待任務block的所有迭代完成后再返回.如果目標隊列是由dispatch_get_global_queue返回的并發(fā)隊列,則可以并發(fā)調(diào)用該block,因此它必須是reentrant安全的.在并發(fā)隊列中使用此函數(shù)可以作為一種有效的并行for循環(huán).
迭代的當前索引被傳遞給block的每次調(diào)用.

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // dispatch_apply是同步的
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"同步index:%zu %@", index, [NSThread currentThread]);
    });

    // 如果想異步,包裝一層
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"異步index:%zu %@", index, [NSThread currentThread]);
        });
    });
}

4.5 dispatch_group: 隊列組

  • 調(diào)用隊列組的dispatch_group_async先把任務放到隊列中,然后將隊列放入隊列組中.或者使用隊列組的dispatch_group_enter讥耗、dispatch_group_leave組合來實現(xiàn)
  • 調(diào)用隊列組的dispatch_group_notify回到指定線程執(zhí)行任務.或者使用dispatch_group_wait回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)
  • dispatch_group_notify: 監(jiān)聽group中任務的完成狀態(tài),當所有的任務都執(zhí)行完成后,追加任務到group中,并執(zhí)行任務
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務2, %@", [NSThread currentThread]);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務 1打厘、任務 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務3, %@", [NSThread currentThread]);
        NSLog(@"group任務完成");
    });
}

執(zhí)行結(jié)果:

任務2, <NSThread: 0x281f87280>{number = 5, name = (null)}
任務1, <NSThread: 0x281fa0c00>{number = 8, name = (null)}
任務3, <NSThread: 0x281fc4d80>{number = 1, name = main}
group任務完成
  • dispatch_group_wait:暫停當前線程(阻塞當前線程),等待指定的group中的任務執(zhí)行完成后,才會往下繼續(xù)執(zhí)行
- (void)group {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務2, %@", [NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group任務完成");
 }

執(zhí)行結(jié)果:

任務2, <NSThread: 0x2817f9540>{number = 4, name = (null)}
任務1, <NSThread: 0x2817ff9c0>{number = 6, name = (null)}
group任務完成
  • dispatch_group_enter(), dispatch_group_leave
- (void)group1 {
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務2, %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務 1蚪缀、任務 2 都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務3, %@", [NSThread currentThread]);
        NSLog(@"group任務完成");
    });
}

執(zhí)行結(jié)果:

任務2, <NSThread: 0x281df3e40>{number = 4, name = (null)}
任務1, <NSThread: 0x281de3f80>{number = 7, name = (null)}
任務3, <NSThread: 0x281db0d80>{number = 1, name = main}
group任務完成

4.6 dispatch_semaphore: 信號量

dispatch_semaphoreGCD中的信號量,持有計數(shù)的信號, dispatch Semaphore中,使用計數(shù)來完成這個功能,計數(shù)小于0時等待,不可通過.計數(shù)為0或大于0時可通過.

主要使用:

  • 保持線程同步,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務
  • 保證線程安全,為線程加鎖

dispatch_semaphore三個方法:

  • dispatch_semaphore_create: 創(chuàng)建一個semaphore并初始化信號的總量
  • dispatch_semaphore_signal: 發(fā)送一個信號,信號計數(shù) + 1
  • dispatch_semaphore_wait: 可以使總信號量 - 1,信號總量小于0時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行
- (void)semaphor {
    
    NSLog(@"當前線程:%@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"任務1, %@", [NSThread currentThread]);

        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"當前線程1:%@", [NSThread currentThread]);

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"任務完成");

}

執(zhí)行結(jié)果:

當前線程:<NSThread: 0x282784d80>{number = 1, name = main}
當前線程1:<NSThread: 0x282784d80>{number = 1, name = main}
任務1, <NSThread: 0x2827d6cc0>{number = 6, name = (null)}
任務完成

從打印結(jié)果可知執(zhí)行流程為:

  • semaphore開始計數(shù)為0
  • 異步任務加入隊列之后,不等待繼續(xù)執(zhí)行, 執(zhí)行到dispatch_semaphore_wait方法, 信號量計數(shù)- 1-1小于0,當前線程進入等待狀態(tài)
  • 任務1執(zhí)行開始執(zhí)行, 執(zhí)行完成后,執(zhí)行dispatch_semaphore_signal,信號量計數(shù)+ 10,阻塞線程恢復繼續(xù)執(zhí)行

完整代碼見GitHub->多線程(附大廠面試講解)


如有不足之處,歡迎予以指正, 如果感覺寫的不錯,記得給個贊呦!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恕出,隨后出現(xiàn)的幾起案子询枚,更是在濱河造成了極大的恐慌,老刑警劉巖浙巫,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金蜀,死亡現(xiàn)場離奇詭異刷后,居然都是意外死亡,警方通過查閱死者的電腦和手機渊抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門尝胆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來护桦,“玉大人含衔,你說我怎么就攤上這事二庵√叭荆” “怎么了催享?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵杭隙,是天一觀的道長。 經(jīng)常有香客問我因妙,道長痰憎,這世上最難降的妖魔是什么攀涵? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮汁果,結(jié)果婚禮上涡拘,老公的妹妹穿的比我還像新娘。我一直安慰自己据德,他們只是感情好鳄乏,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布棘利。 她就那樣靜靜地躺著橱野,像睡著了一般善玫。 火紅的嫁衣襯著肌膚如雪水援。 梳的紋絲不亂的頭發(fā)上茅郎,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天蜗元,我揣著相機與錄音系冗,去河邊找鬼奕扣。 笑死掌敬,一個胖子當著我的面吹牛惯豆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播楷兽,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芯杀!你這毒婦竟也來了端考?” 一聲冷哼從身側(cè)響起瘪匿,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棋弥,沒想到半個月后核偿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顽染,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年粉寞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唧垦。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖振亮,靈堂內(nèi)的尸體忽然破棺而出巧还,到底是詐尸還是另有隱情坊秸,我是刑警寧澤麸祷,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布褒搔,位于F島的核電站阶牍,受9級特大地震影響星瘾,放射性物質(zhì)發(fā)生泄漏走孽。R本人自食惡果不足惜琳状,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望算撮。 院中可真熱鬧生宛,春花似錦肮柜、人聲如沸陷舅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芒澜。三九已至仰剿,卻和暖如春痴晦,著一層夾襖步出監(jiān)牢的瞬間南吮,已是汗流浹背誊酌。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工部凑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碧浊,地道東北人涂邀。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓箱锐,卻偏偏與公主長得像比勉,于是被迫代替她去往敵國和親驹止。 傳聞我的和親對象是個殘疾皇子浩聋,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容