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í)行的。因為同步任務(wù)的完成時間是不可控的,如果在同步的過程中無法進(jìn)行別的任務(wù)(線程)那就太糟糕了!

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

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

多線程概述

多線程的實現(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下主要成本包括:在??臻g的子線程512KB、主線程1MB,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間。

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

線程越多,程序設(shè)計就越復(fù)雜:因為要考慮到線程之間的通信,多線程的數(shù)據(jù)共享。

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

1. iOS的主線程

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

主線程的作用:?

1>顯示\刷新UI界面;

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

主線程的使用注意事項:

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

2. iOS子線程

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

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īng)該將網(wǎng)絡(luò)加載放在異步執(zhí)行,執(zhí)行成功后,再回到主線程顯示加載后的圖片(詳細(xì)做法馬上就會講到)。

3. GCD的使用步驟

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

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

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

4. 什么是隊列?

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

GCD的隊列可以分為2大類型:

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

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

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

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

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

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

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

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

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

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

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

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

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

dispatch_queue_tqueue = dispatch_queue_create("serial_queue",NULL);

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

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

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

dispatch_queue_tqueue = dispatch_get_main_queue();

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

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

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

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

GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊列,供整個應(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(在主線程)。

為了實現(xiàn)這個需求,我們需要首先拿到全局并發(fā)隊列(或自己開啟一個子線程)來執(zhí)行耗時的操作,然后在其完成block中拿到全局串行隊列來執(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í)行了。

為了實現(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ù)。

為了實現(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)。因為任務(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ù)。

為了實現(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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