iOS多線程技術(shù)-GCD(一)

進(jìn)程與線程

在了解多線程之前,需要弄清進(jìn)程和線程的概念和他們之間的區(qū)別咙边。

進(jìn)程:

系統(tǒng)中正在運(yùn)行的一個程序,進(jìn)程之間是相互獨(dú)立的次员,每個進(jìn)程都有屬于自己的內(nèi)存空間败许。比如手機(jī)中的微信應(yīng)用和印象筆記應(yīng)用,他們都是iOS系統(tǒng)中獨(dú)立的進(jìn)程翠肘,有著自己的內(nèi)存空間檐束。

線程:

進(jìn)程內(nèi)部執(zhí)行任務(wù)所需要的執(zhí)行路徑。進(jìn)程若想執(zhí)行任務(wù)束倍,則必須得在線程下執(zhí)行被丧。也就是說進(jìn)程至少有一個線程才能執(zhí)行任務(wù)。但是绪妹,我們使用軟件的時候甥桂,很少有只讓它做一件事的時候:

舉個印象筆記的?? : 當(dāng)你正在編輯一則筆記的時候點(diǎn)擊了同步按鈕,那么編輯任務(wù)(線程)和同步任務(wù)(線程)一定是不能按照順序執(zhí)行的邮旷。因?yàn)橥饺蝿?wù)的完成時間是不可控的黄选,如果在同步的過程中無法進(jìn)行別的任務(wù)(線程)那就太糟糕了!

因此婶肩,我們需要讓一些任務(wù)可以同時進(jìn)行办陷。既然任務(wù)是在線程上執(zhí)行的,那么多任務(wù)的執(zhí)行就意味著需要多線程的開啟和使用律歼。

來一張圖直觀地展示一下內(nèi)存民镜,進(jìn)程和線程的關(guān)系:

多線程概述

多線程的實(shí)現(xiàn)原理:雖然在同一時刻,CPU只能處理1條線程险毁,但是CPU可以快速地在多條線程之間調(diào)度(切換)制圈,造成了多線程并發(fā)執(zhí)行的假象。

1. 多線程的優(yōu)點(diǎn)

能適當(dāng)提高程序的執(zhí)行效率畔况。

能適當(dāng)提高資源利用率(CPU鲸鹦、內(nèi)存利用率)。

2. 多線程的缺點(diǎn)

創(chuàng)建線程是需要成本的:iOS下主要成本包括:在楑喂颍空間的子線程512KB馋嗜、主線程1MB,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間域庇。

線程越多嵌戈,CPU在調(diào)度線程上的開銷就越大覆积。

線程越多听皿,程序設(shè)計(jì)就越復(fù)雜:因?yàn)橐紤]到線程之間的通信熟呛,多線程的數(shù)據(jù)共享。

多線程在iOS中應(yīng)用

1. iOS的主線程

一個iOS程序運(yùn)行后尉姨,默認(rèn)會開啟1條線程庵朝,稱為“主線程”或“UI線程”

主線程的作用:?

1>顯示\刷新UI界面;

2>處理UI事件(比如點(diǎn)擊事件、滾動事件又厉、拖拽事件等)

主線程的使用注意事項(xiàng):

不能把比較耗時的操作放到主線程中九府,嚴(yán)重影響UI的流暢度,給用戶一種程序“卡頓”的體驗(yàn)覆致。因此侄旬,要將耗時的操作放在子線程中異步執(zhí)行。這樣一來煌妈,及時開始執(zhí)行了耗時的操作儡羔,也不會影響主線程中UI交互的體驗(yàn)。

2. iOS子線程

子線程是異步執(zhí)行的璧诵,不影響主線程汰蜘。在iOS開發(fā)中,我們需要將耗時的任務(wù)(網(wǎng)絡(luò)請求之宿,復(fù)雜的運(yùn)算)放在子線程進(jìn)行族操,不讓其影響UI的交互體驗(yàn)。

3.多線程安全

當(dāng)多個線程訪問同一塊資源時比被,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題色难。就好比好幾個人在同時修改同一個表格,造成數(shù)據(jù)的錯亂等缀。

3.1 資源搶奪的解決方案

我們需要給數(shù)據(jù)添加互斥鎖枷莉。也就是說,當(dāng)某線程訪問一個數(shù)據(jù)之前就要給數(shù)據(jù)加鎖项滑,讓其不被其他的線程所修改依沮。就好比一個人修改表格的時候給表格設(shè)置了密碼,那么其他人就無法訪問文件了枪狂。當(dāng)他修改文件之后危喉,再講密碼撤銷,第二個人就可以訪問該文件了州疾。

注意:這里的線程都為子線程辜限,如果給數(shù)據(jù)加了鎖,就等于將這些異步的子線程變成同步的了严蓖,這也叫做線程同步技術(shù)薄嫡。

3.2 互斥鎖使用:

@synchronized(鎖對象) {// 需要鎖定的代碼? };

3.3 互斥鎖的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題

缺點(diǎn):需要消耗大量的CPU資源

互斥鎖的使用前提:多條線程搶奪同一塊資源的時候使用氧急。

3.4互斥鎖在iOS開發(fā)中的使用

OC在定義屬性時有nonatomic和atomic兩種選擇

atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)

nonatomic:非原子屬性毫深,不會為setter方法加鎖

3.5 nonatomic和atomic對比

atomic:線程安全吩坝,需要消耗大量的資源

nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備

建議:?所有屬性都聲明為nonatomic哑蔫,盡量避免多線程搶奪同一塊資源钉寝,將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理闸迷,減小移動客戶端的壓力嵌纲。

多線程在iOS 中應(yīng)用-GCD

GCD,全稱為 Grand Central Dispatch 腥沽,是iOS用來管理線程的技術(shù)逮走。 純C語言,提供了非常多強(qiáng)大的函數(shù)今阳。

1. GCD的優(yōu)勢

GCD會自動利用更多的CPU內(nèi)核(比如雙核师溅、四核)。

GCD會自動管理線程的生命周期(創(chuàng)建線程酣栈、調(diào)度任務(wù)险胰、銷毀線程)。

程序員只需要告訴GCD想要執(zhí)行什么任務(wù)矿筝,不需要編寫任何線程管理代碼起便。

2. 為什么要用GCD?

為了要提高軟件性能窖维,應(yīng)該異步執(zhí)行耗時任務(wù)(加載圖片)榆综,以防止影響主線程任務(wù)的執(zhí)行(UI相應(yīng))。舉個?? :

從網(wǎng)絡(luò)加載一張圖片铸史,如果將此任務(wù)放到主線程鼻疮,那么在下載完成的時間里,軟件是無法相應(yīng)用戶的任何操作的琳轿。特別地判沟,如果當(dāng)前是在可以滾動的頁面,就會造成無法滾動這種體驗(yàn)非常糟的情況崭篡。

所以:應(yīng)該將網(wǎng)絡(luò)加載放在異步執(zhí)行挪哄,執(zhí)行成功后,再回到主線程顯示加載后的圖片(詳細(xì)做法馬上就會講到)琉闪。

3. GCD的使用步驟

由開發(fā)者定制將要執(zhí)行的任務(wù)迹炼。

將任務(wù)添加到隊(duì)列中,GCD會自動將隊(duì)列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行斯入。

注意:任務(wù)的取出遵循隊(duì)列的FIFO原則:先進(jìn)先出砂碉,后進(jìn)后出。

4. 什么是隊(duì)列刻两?

隊(duì)列是用來存放任務(wù)的增蹭,由GCD將這些任務(wù)從隊(duì)列中取出并放到相應(yīng)的線程中執(zhí)行。

GCD的隊(duì)列可以分為2大類型:

1>.并發(fā)隊(duì)列(Concurrent Dispatch Queue)

可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))闹伪,并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

2>. 串行隊(duì)列(Serial Dispatch Queue)

讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后沪铭,再執(zhí)行下一個任務(wù))壮池。

那么隊(duì)列和線程又有什么區(qū)別偏瓤?

簡單來說,隊(duì)列就是用來存放任務(wù)的“暫存區(qū)”椰憋,而線程是執(zhí)行任務(wù)的路徑厅克,GCD將這些存在于隊(duì)列的任務(wù)取出來放到相應(yīng)的線程上去執(zhí)行,而隊(duì)列的性質(zhì)決定了在其中的任務(wù)在哪種線程上執(zhí)行橙依。

下面由一張圖來直觀地展示任務(wù)证舟,隊(duì)列和線程的關(guān)系:

在這里,我們可以看到窗骑,放入串行隊(duì)列的任務(wù)會一個一個地執(zhí)行女责。而放入并行隊(duì)列的任務(wù),會在多個線程并發(fā)地執(zhí)行创译。

5. 隊(duì)列的創(chuàng)建

5.1 串行隊(duì)列的創(chuàng)建:

GCD中獲得串行有2種途徑:

1>.使用dispatch_queue_create函數(shù)創(chuàng)建串行隊(duì)列

// 創(chuàng)建串行隊(duì)列(隊(duì)列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)

dispatch_queue_tqueue = dispatch_queue_create("serial_queue",NULL);

2>.使用主隊(duì)列(跟主線程相關(guān)聯(lián)的隊(duì)列)

主隊(duì)列是GCD自帶的一種特殊的串行隊(duì)列:放在主隊(duì)列中的任務(wù)抵知,都會放到主線程中執(zhí)行。

可以使用dispatch_get_main_queue()獲得系統(tǒng)提供的主隊(duì)列:

dispatch_queue_tqueue = dispatch_get_main_queue();

5.2 并發(fā)隊(duì)列的創(chuàng)建:

1>.使用dispatch_queue_create函數(shù)創(chuàng)建并發(fā)隊(duì)列软族。

dispatch_queue_tqueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

2>.使用dispatch_get_global_queue獲得全局并發(fā)隊(duì)列刷喜。

GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊(duì)列,供整個應(yīng)用使用立砸,可以無需手動創(chuàng)建掖疮。

dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

6. GCD的幾種重要的應(yīng)用

6.1 子線程與主線程的通信

需求點(diǎn):我們有時需要在子線程處理一個耗時比較長的任務(wù),而且此任務(wù)完成后颗祝,要在主線程執(zhí)行另一個任務(wù)浊闪。

例子:從網(wǎng)絡(luò)加載圖片(在子線程),加載完成就更新UIView(在主線程)螺戳。

為了實(shí)現(xiàn)這個需求搁宾,我們需要首先拿到全局并發(fā)隊(duì)列(或自己開啟一個子線程)來執(zhí)行耗時的操作,然后在其完成block中拿到全局串行隊(duì)列來執(zhí)行UI刷新的任務(wù)温峭。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

????????????//加載圖片

????????NSData*dataFromURL = [NSDatadataWithContentsOfURL:imageURL];

????????UIImage*imageFromData = [UIImageimageWithData:dataFromURL];

????????dispatch_async(dispatch_get_main_queue(), ^{

????????????//加載完成更新view

????????????UIImageView*imageView = [[UIImageViewalloc] initWithImage:imageFromData];

? ????? });? ? ?

});

以筆者的拙見猛铅,除了復(fù)雜的算法,網(wǎng)絡(luò)請求以外凤藏,大多數(shù)dataWithContentsOf奸忽。堕伪。。函數(shù)可能也會比較耗時栗菜,所以以后遇到與NSData交互的操作時欠雌,盡量將其放在子線程執(zhí)行。

6.2 dispatch_once

需求點(diǎn):用于在程序啟動到終止疙筹,只執(zhí)行一次的代碼富俄。此代碼被執(zhí)行后,相當(dāng)于自身全部被加上了注釋而咆,不會再執(zhí)行了霍比。

為了實(shí)現(xiàn)這個需求,我們需要使用dispatch_once讓代碼在運(yùn)行一次后即刻被“雪藏”暴备。

//使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次

staticdispatch_once_tonceToken;

dispatch_once(&onceToken, ^{

// 只執(zhí)行1次的代碼悠瞬,這里默認(rèn)是線程安全的:不會有其他線程可以訪問到這里

});

6.3 dispatch_group

需求點(diǎn):執(zhí)行多個耗時的異步任務(wù),但是只能等到這些任務(wù)都執(zhí)行完畢后涯捻,才能在主線程執(zhí)行某個任務(wù)浅妆。

為了實(shí)現(xiàn)這個需求,我們需要讓將這些異步執(zhí)行的操作放在dispatch_group_async函數(shù)中執(zhí)行障癌,最后再調(diào)用dispatch_group_notify來執(zhí)行最后執(zhí)行的任務(wù)凌外。

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í)行完畢后,回到主線程...

});

讓我們看一下示例代碼和運(yùn)行結(jié)果:

示例代碼:

為了使對比明顯涛浙,筆者多開了幾條線程康辑,這樣更容易看清問題。

dispatch_group_t group =? dispatch_group_create(); ??

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

// 執(zhí)行1個耗時的異步操作

for(NSIntegerindex =0; index <10000; index ++) {

? ? }

NSLog(@"完成了任務(wù)1");

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

// 執(zhí)行1個耗時的異步操作

for(NSIntegerindex =0; index <20000; index ++) {

? ? }

NSLog(@"完成了任務(wù)2");

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

// 執(zhí)行1個耗時的異步操作

for(NSIntegerindex =0; index <200000; index ++) {

? ? }

NSLog(@"完成了任務(wù)3");

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

// 執(zhí)行1個耗時的異步操作

for(NSIntegerindex =0; index <400000; index ++) {

? ? }

NSLog(@"完成了任務(wù)4");

});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

// 執(zhí)行1個耗時的異步操作

for(NSIntegerindex =0; index <1000000; index ++) {

? ? }

NSLog(@"完成了任務(wù)5");

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

// 等前面的異步操作都執(zhí)行完畢后蝗拿,回到主線程...

NSLog(@"都完成了");

});

運(yùn)行結(jié)果:

從三次運(yùn)行的結(jié)果來看:

異步執(zhí)行的任務(wù)1-5的最終完成時間是與其自身完成任務(wù)所需要的時間并無絕對關(guān)聯(lián)晾捏。因?yàn)槿蝿?wù)5是最耗時的,它在第一次運(yùn)行結(jié)果里并不是最后才完成的哀托。任務(wù)1是最不耗時的惦辛,但是它在第二次運(yùn)行結(jié)果里也不是最先完成的。

異步執(zhí)行的任務(wù)1-5無論完成順序如何仓手,只有當(dāng)他們都完成后才會調(diào)用主線程的打印“都完成了”胖齐。

6.4 dispatch_barrier

需求點(diǎn):雖然我們有時要執(zhí)行幾個不同的異步任務(wù),但是我們還是要將其分成兩組:當(dāng)?shù)谝唤M異步任務(wù)都執(zhí)行完成后才執(zhí)行第二組的異步任務(wù)嗽冒。這里的組可以包含一個任務(wù)呀伙,也可以包含多個任務(wù)。

為了實(shí)現(xiàn)這個需求添坊,我們需要使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);在兩組任務(wù)之間形成“柵欄”剿另,使其“下方”的異步任務(wù)在其“上方”的異步任務(wù)都完成之前是無法執(zhí)行的。

dispatch_queue_tqueue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

NSLog(@"----任務(wù) 1-----");

});

dispatch_async(queue, ^{

NSLog(@"----任務(wù) 2-----");

});? ?

dispatch_barrier_async(queue, ^{

NSLog(@"----barrier-----");

});

dispatch_async(queue, ^{

NSLog(@"----任務(wù) 3-----");

});

dispatch_async(queue, ^{

NSLog(@"----任務(wù) 4-----");

});

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市雨女,隨后出現(xiàn)的幾起案子谚攒,更是在濱河造成了極大的恐慌,老刑警劉巖氛堕,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馏臭,死亡現(xiàn)場離奇詭異,居然都是意外死亡讼稚,警方通過查閱死者的電腦和手機(jī)括儒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锐想,“玉大人帮寻,你說我怎么就攤上這事⊥匆校” “怎么了规婆?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝉稳。 經(jīng)常有香客問我,道長掘鄙,這世上最難降的妖魔是什么耘戚? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮操漠,結(jié)果婚禮上收津,老公的妹妹穿的比我還像新娘。我一直安慰自己浊伙,他們只是感情好撞秋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嚣鄙,像睡著了一般吻贿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哑子,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天舅列,我揣著相機(jī)與錄音,去河邊找鬼卧蜓。 笑死帐要,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弥奸。 我是一名探鬼主播榨惠,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了赠橙?” 一聲冷哼從身側(cè)響起伸蚯,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎简烤,沒想到半個月后剂邮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡横侦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年挥萌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片引瀑。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出掸宛,到底是詐尸還是另有隱情别凤,我是刑警寧澤求豫,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布是晨,位于F島的核電站蚊逢,受9級特大地震影響檬寂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一伪窖、第九天 我趴在偏房一處隱蔽的房頂上張望汹买。 院中可真熱鬧耙蔑,春花似錦见妒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钱豁,卻和暖如春耻卡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牲尺。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工卵酪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幌蚊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓溃卡,卻偏偏與公主長得像溢豆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瘸羡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

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