iOS開發(fā):多線程與GCD

一扶欣、多線程

1.1進(jìn)程與線程

進(jìn)程:進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序;每個(gè)進(jìn)程之間是獨(dú)立的千扶,每個(gè)進(jìn)程均運(yùn)行在其專用的受保護(hù)的內(nèi)存空間內(nèi)宵蛀。

線程:線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行;進(jìn)程要想執(zhí)行任務(wù)县貌,必須要有線程术陶,進(jìn)程至少要有一條線程;程序啟動(dòng)回默認(rèn)開啟一條線程,即主線程煤痕。

多線程原理:同一時(shí)間單核CPU只能處理一個(gè)線程梧宫,即只有一個(gè)線程在執(zhí)行。多線程同時(shí)執(zhí)行是CPU快速的在多個(gè)線程之間切換摆碉,CPU調(diào)度線程的時(shí)間足夠快塘匣,就造成了多線程同時(shí)執(zhí)行的效果。如果線程非常多巷帝,CPU會(huì)在N個(gè)線程之間切換忌卤,消耗大量的CPU資源,每個(gè)線程被調(diào)度的次數(shù)會(huì)降低楞泼,線程的執(zhí)行效率也會(huì)降低驰徊。

多線程技術(shù)方案

方案 說明 語言 生命周期 使用頻率
pthread 一套通用的多線程API;適用于Unix/Linux/Windows等平臺(tái);跨平臺(tái),可移植堕阔;適用難度大 c 開發(fā)管理 很低
NSThread 使用更加面向?qū)ο蠊鞒В缓?jiǎn)單易用,可直接操作線程對(duì)象 OC 開發(fā)管理
GCD 旨在替代NSThread技術(shù)超陆,充分利用設(shè)備的多核 c 自動(dòng)管理
NSOperation 基于GCD(底層是GCD)牺弹;比GCD多了一些更簡(jiǎn)單實(shí)用的功能;實(shí)用更加面向?qū)ο?/td> OC 自動(dòng)管理

1.2 任務(wù)

任務(wù)是指執(zhí)行的操作,簡(jiǎn)單說就是在線程中執(zhí)行的那段代碼张漂。在GCD中是放在block中的晶默。

任務(wù)的執(zhí)行有兩種方式:同步執(zhí)行和異步執(zhí)行。兩者的區(qū)別主要在于是否等待隊(duì)列中的任務(wù)執(zhí)行完畢航攒,以及是否具備開啟新線程的能力荤胁。

  • 同步執(zhí)行(sync):同步添加任務(wù)到指定的隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束前會(huì)一直等待屎债,直到隊(duì)列里的任務(wù)完成后再繼續(xù)執(zhí)行;只能在當(dāng)前線程執(zhí)行任務(wù)垢油,不具備開啟新線程的能力盆驹。加入方式dispatch_sync
  • 異步執(zhí)行(async):異步添加任務(wù)到指定隊(duì)列滩愁,不會(huì)做任何等待躯喇,可以繼續(xù)執(zhí)行任務(wù);可以在新的線程執(zhí)行任務(wù)硝枉,具備開啟新線程的能力廉丽,但是并不一定開啟新線程,這跟任務(wù)所在的隊(duì)列有關(guān)妻味。加入方式dispatch_async正压。

任務(wù)執(zhí)行速度的影響因素:1.CPU。2.線程狀態(tài)责球。3.任務(wù)的復(fù)雜度焦履。4.任務(wù)的優(yōu)先級(jí)。其中任務(wù)的優(yōu)先級(jí)包括用戶指定的qualityServiceuserInteractive雏逾, userInitiated嘉裤,utilitybackground栖博,default)屑宠;等待的頻繁程度(不執(zhí)行)。

二仇让、GCD

隊(duì)列(Dispatch Queue)指的是執(zhí)行任務(wù)的等待隊(duì)列典奉,即用來存放任務(wù)的隊(duì)列。隊(duì)列采用FIFO(先進(jìn)先出)的原則丧叽,新任務(wù)總是被插入到隊(duì)列的末尾秋柄,讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取,每讀取一個(gè)任務(wù)蠢正,則從隊(duì)列中釋放一個(gè)任務(wù)骇笔。

GCD中有2種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列谆焊,兩者均遵循FIFO的原則脐瑰,不同點(diǎn)在于執(zhí)行的順序以及開啟線程的數(shù)量。

  • 串行隊(duì)列(Serial Dispatch Queue):只開啟一個(gè)線程,一個(gè)任務(wù)執(zhí)行完畢才會(huì)執(zhí)行下一個(gè)任務(wù)幻碱。每次只有一個(gè)任務(wù)被執(zhí)行。
  • 并發(fā)隊(duì)列(Concurrent Dispatch Queue):可以開啟多個(gè)線程院尔,并且同時(shí)執(zhí)行任務(wù)御板。可以讓多個(gè)任務(wù)同時(shí)執(zhí)行虚吟。

隊(duì)列的創(chuàng)建:可以使用dispatch_queue_create創(chuàng)建寸认,該方法需要傳入2個(gè)參數(shù):第一個(gè)參數(shù)表示隊(duì)列的標(biāo)識(shí),可為空串慰;第二個(gè)參數(shù)用來識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列偏塞,DISPATCH_QUEUE_SERIAL(==NULL)標(biāo)識(shí)串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT

//串行隊(duì)列
dispatch_queue_t s = dispatch_queue_create("com.appex.queue.serial", DISPATCH_QUEUE_SERIAL);
//并發(fā)隊(duì)列
dispatch_queue_t c = dispatch_queue_create("com.appex.queue.concurrent", DISPATCH_QUEUE_CONCURRENT);

主隊(duì)列:主隊(duì)列(Main Dispatch Queue)是一種特殊的串行隊(duì)列邦鲫,說它特殊是因?yàn)槟J(rèn)情況下代碼就在主隊(duì)列中灸叼,主隊(duì)列的代碼又都會(huì)放在主線程中執(zhí)行。獲取方式:

//獲取主隊(duì)列
dispatch_group_t main = dispatch_get_main_queue();

全局并發(fā)隊(duì)列:全局并發(fā)隊(duì)列是(Global Dispatch Queue)是系統(tǒng)提供的并發(fā)隊(duì)列庆捺,獲取方法:

//獲取全局并發(fā)隊(duì)列
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//第一個(gè)參數(shù)表示優(yōu)先級(jí)的高低古今,一般傳`DISPATCH_QUEUE_PRIORITY_DEFAULT`
DISPATCH_QUEUE_PRIORITY_HIGH       
DISPATCH_QUEUE_PRIORITY_DEFAULT    
DISPATCH_QUEUE_PRIORITY_LOW      
DISPATCH_QUEUE_PRIORITY_BACKGROUND 
//第二個(gè)參數(shù)暫時(shí)沒用,傳0即可滔以。

如果當(dāng)前在主線程捉腥,按照隊(duì)列的串行和并發(fā),任務(wù)的同步和異步特性組合你画,我們歸納如下:

區(qū)別 并發(fā)隊(duì)列 串行隊(duì)列 主隊(duì)列
同步 沒有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù) 死鎖
異步 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條),串行執(zhí)行任務(wù) 沒有開啟新線程,串行執(zhí)行任務(wù)
/*死鎖案例-1*/
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
    /*async沒有開啟新線程但狭,以下代碼在主線程中運(yùn)行*/
    NSLog(@"開始:%@", [NSThread currentThread]);
    dispatch_sync(queue, ^{
         NSLog(@"sync:%@", [NSThread currentThread]);
     });
     NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});

在主線程中,向主隊(duì)列添加同步任務(wù)會(huì)死鎖撬即。這是因?yàn)樘砑拥娜蝿?wù)和主隊(duì)列自身的任務(wù)相互等待立磁,阻塞了主隊(duì)列,最終造成主隊(duì)列所載的線程(主線程)死鎖剥槐。如果在其他線程向主隊(duì)列添加同步任務(wù)唱歧,則不會(huì)死鎖。

/*死鎖案例-2*/
dispatch_queue_t queue = dispatch_queue_create("com.app.serial", 0);
dispatch_async(queue, ^{
    /*async開啟1條新線程粒竖,以下代碼在子線程中運(yùn)行*/
    NSLog(@"開始:%@", [NSThread currentThread]);
    dispatch_sync(queue, ^{
         NSLog(@"sync:%@", [NSThread currentThread]);
   });
   NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});

在子線程中颅崩,向串行隊(duì)列添加同步任務(wù)會(huì)死鎖。這是因?yàn)樘砑拥娜蝿?wù)和串行隊(duì)列自身的任務(wù)相互等待蕊苗,阻塞了串行隊(duì)列沿后,最終造成串行隊(duì)列所在的線程(子線程)死鎖。如果在其他線程向該隊(duì)列添加同步任務(wù)朽砰,則不會(huì)死鎖尖滚。

以上案例可以概括為在一個(gè)串行隊(duì)列所在的線程喉刘,向該隊(duì)列添加同步任務(wù)會(huì)造成串行隊(duì)列追加的任務(wù)和原有的任務(wù)相互等待而阻塞當(dāng)前線程。

三漆弄、GCD其他常用函數(shù)

3.1 dispatch_after:表示在某個(gè)隊(duì)列中異步延遲執(zhí)行任務(wù)

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t work){
    _dispatch_after(when, queue, NULL, work, true);
}

第一個(gè)參數(shù)when表示開始的時(shí)間睦裳,通常在現(xiàn)在的時(shí)間時(shí)間的基礎(chǔ)上加時(shí)間,如dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2)表示2秒之后撼唾;第二個(gè)參數(shù)queue傳入隊(duì)列廉邑,第三個(gè)參數(shù)work傳入任務(wù)的block代碼。
常規(guī)用法如下:

NSLog(@"開始:%@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
   NSLog(@"after:%@", [NSThread currentThread]);
});

3.2 dispatch_once:代碼只執(zhí)行一次

void dispatch_once(dispatch_once_t *val, dispatch_block_t block){
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}

第一個(gè)參數(shù)val傳入一個(gè)dispatch_once_t類型的指針地址倒谷,第二個(gè)參數(shù)block傳入只執(zhí)行一次的block任務(wù)蛛蒙。
常規(guī)用法:

@interface NXDownloader : NSObject
+ (NXDownloader *)downloader;
@end

@implementation NXDownloader
+ (NXDownloader *)downloader{
    static dispatch_once_t t;
    static NXDownloader *sharedInstance;
    dispatch_once(&t, ^{
        sharedInstance = [[NXDownloader alloc] init];
    });
    return sharedInstance;
}
@end

這樣我們通過NXDownloader *downloader = [NXDownloader downloader];獲取到的實(shí)例都是同一個(gè),而且是線程安全的渤愁。

3.3dispatch_barrier_async/dispatch_barrier_sync:柵欄函數(shù)

柵欄函數(shù)的作用就是隔離柵欄函數(shù)之前與之后的代碼執(zhí)行牵祟,只有前面的代碼執(zhí)行完畢才會(huì)執(zhí)行后面的代碼,函數(shù)如下:

void dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work){
    ...
}

void dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work){
    ...
}

柵欄函數(shù)的第一個(gè)參數(shù)dq接收一個(gè)隊(duì)列猴伶,按照代碼注釋的說明,這里需要傳入一個(gè)并發(fā)隊(duì)列才會(huì)真正的發(fā)揮柵欄函數(shù)的功能塌西,如果傳入的dq是個(gè)串行隊(duì)列他挎,則函數(shù)的表現(xiàn)與dispatch_barrier_asyncdispatch_barrier_sync表現(xiàn)一樣。

那么這里的asyncsync的作用有什么不同呢捡需?看如下代碼:

dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"0-0:%@",[NSThread currentThread]);

//dispatch_barrier_async或dispatch_barrier_sync
dispatch_barrier_async(queue, ^{
    sleep(2);
    NSLog(@"barrier:%@", [NSThread currentThread]);
});

NSLog(@"0-1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"4:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"5:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
    sleep(1);
    NSLog(@"6:%@", [NSThread currentThread]);
});

多次打印办桨,結(jié)果如下:


async.png
sync.png

執(zhí)行結(jié)果顯示:{1,2,3}執(zhí)行的順序是不確定,{4,5,6}執(zhí)行順序也是不確定的站辉,可以確定的是{1,2,3}執(zhí)行完畢后執(zhí)行barrier呢撞,再執(zhí)行{4,5,6}。在async的情況下0-0饰剥,0-1最先執(zhí)行殊霞。而sync的情況下0-0在barrier之前執(zhí)行,0-1barrier之后執(zhí)行汰蓉。也就是sync會(huì)如同dispatch_sync一樣執(zhí)行完畢后再執(zhí)行后面的代碼绷蹲。并且在sync的情況下barrier任務(wù)會(huì)在原有的線程(這里是主線程)中執(zhí)行。

還需要注意一點(diǎn)顾孽,柵欄函數(shù)的只在自定義的并發(fā)隊(duì)列才會(huì)生效祝钢,這一點(diǎn)也好理解,因?yàn)椴l(fā)隊(duì)列若厚,系統(tǒng)也會(huì)向里邊添加任務(wù)拦英,我們?cè)O(shè)置一個(gè)柵欄那么后續(xù)加入的系統(tǒng)任務(wù)豈不是要等待柵欄執(zhí)行完畢?這顯然不合理测秸。

3.4dispatch_group疤估,隊(duì)列組

隊(duì)列組簡(jiǎn)言之就是一組任務(wù)執(zhí)行完畢后會(huì)有一個(gè)單獨(dú)的回調(diào)灾常。

//隊(duì)列組的創(chuàng)建
dispatch_group_t group = dispatch_group_create();
//添加任務(wù)
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
    NSLog(@"執(zhí)行");
});
//任務(wù)執(zhí)行完畢的回掉
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      NSLog(@"結(jié)束:%@");
});

其中dispatch_group_async會(huì)把任務(wù)放入隊(duì)列,再把隊(duì)列放入隊(duì)列組。也可以用dispatch_group_enterdispatch_group_leave成對(duì)使用做裙。 dispatch_group_notify會(huì)在任務(wù)執(zhí)行完畢后回調(diào)岗憋,你可以指定一個(gè)隊(duì)列繼續(xù)做其他事情。還有一個(gè)不太常用的dispatch_group_wait函數(shù)锚贱,這個(gè)函數(shù)第一個(gè)參數(shù)傳入一個(gè)group仔戈,第二個(gè)參數(shù)傳入一個(gè)time,這個(gè)時(shí)間指定的是dispatch_group_wait之后的代碼等待的最大時(shí)間,假定這里設(shè)定的時(shí)間是3秒拧廊,如果前面的任務(wù)2秒執(zhí)行完畢监徘,那么wait后面的代碼會(huì)在2秒后執(zhí)行。如果前面的任務(wù)4秒執(zhí)行完畢吧碾,那么wait后面的代碼會(huì)在第3秒的時(shí)候開始執(zhí)行凰盔。

dispatch_group_notify案例

//dispatch_group_notify案例:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);

for (int i= 0; i < 10; i++){
    dispatch_group_async(group, queue, ^{
        NSLog(@"%d:%@", i, [NSThread currentThread]);
    });

    /*以上三行代碼等價(jià)于
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
         NSLog(@"%d:%@", i, [NSThread currentThread]);
         dispatch_group_leave(group);
    });
    */
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});

dispatch_group_wait案例

//dispatch_group_wait案例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);

for (int i= 0; i < 10; i++){
    dispatch_group_async(group, queue, ^{
        sleep(2);
        NSLog(@"%d:%@", i, [NSThread currentThread]);
    });
}
//dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*2)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"結(jié)束:%@", [NSThread currentThread]);

這個(gè)場(chǎng)景在實(shí)際開發(fā)中使用較多,比如現(xiàn)在有一組網(wǎng)絡(luò)圖片需要分享到某個(gè)平臺(tái)倦春,我們需要等待多張網(wǎng)絡(luò)圖片全部下載完畢后才能開始分享户敬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市睁本,隨后出現(xiàn)的幾起案子尿庐,更是在濱河造成了極大的恐慌,老刑警劉巖呢堰,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抄瑟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡枉疼,警方通過查閱死者的電腦和手機(jī)皮假,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骂维,“玉大人惹资,你說我怎么就攤上這事『焦耄” “怎么了布轿?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)来颤。 經(jīng)常有香客問我汰扭,道長(zhǎng),這世上最難降的妖魔是什么福铅? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任萝毛,我火速辦了婚禮,結(jié)果婚禮上滑黔,老公的妹妹穿的比我還像新娘笆包。我一直安慰自己环揽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布庵佣。 她就那樣靜靜地躺著歉胶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巴粪。 梳的紋絲不亂的頭發(fā)上通今,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音肛根,去河邊找鬼辫塌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛派哲,可吹牛的內(nèi)容都是我干的臼氨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼芭届,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼储矩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起褂乍,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤持隧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后树叽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舆蝴,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谦絮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年题诵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片层皱。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡性锭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叫胖,到底是詐尸還是另有隱情草冈,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布瓮增,位于F島的核電站怎棱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绷跑。R本人自食惡果不足惜拳恋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砸捏。 院中可真熱鬧谬运,春花似錦隙赁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轰驳,卻和暖如春厚掷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滑废。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工蝗肪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蠕趁。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓薛闪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親俺陋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豁延,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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