為什么需要并發(fā)
假設您在主線程上并且需要來自服務器的數據。您從服務器請求數據并等待,直到您從服務器獲得響應。在此期間,您的主線程不會執(zhí)行任何與 UI 相關的工作,這會使您的應用程序無響應。
假設服務器在此期間需要 10 秒才能給出響應,如果用戶點擊按鈕,系統(tǒng)將不會響應它,這對用戶來說非常糟糕。
如果你可以在同一時間(或大約在同一時間)運行的這兩個任務,一個線程專門處理用戶界面相關的工作,其他的線程處理耗時任務。這樣一來,上面的情況就不會發(fā)生了。
并發(fā)
并發(fā)意味值應用程序可以使用分割時間的方式同時處理多個任務。如果一臺計算機只有一個CPU,那么它無法在一個精確的時間點上同時運行多個任務,但是可以通過上下文切換的方式在一段時間(很短,比如1s)內執(zhí)行多個任務。
上下文切換是指存儲線程的狀態(tài),并在將來恢復這個狀態(tài)繼續(xù)執(zhí)行。這允許多個進程共享一個CPU資源,同時這也是多任務操作系統(tǒng)的基本功能。
并行
并行指多個任務同時發(fā)生,并沒有上下文切換。
對于我們討論的情況,如果并行地執(zhí)行網絡調用,那么將有兩個線程在兩個不同的內核上執(zhí)行主線程和后臺指令,與前一個相比,速度非???,但需要額外的物理要求-需要CPU有多核。
如下圖所示,在并行的情況下,兩條線程真的在同時執(zhí)行;而并發(fā),在一個線程執(zhí)行時,另一個在休眠。
關于線程的小知識
在單核CPU上,如果你創(chuàng)建了10條線程,那么它只能使用并發(fā)/時間片分割/上下文切換的方式執(zhí)行它們
在10核CPU上,如果你創(chuàng)建了10條線程,那么它們可能以下面的方式執(zhí)行:
使用上下文切換的方式在1個核心上并發(fā)執(zhí)行
每個線程在獨立的核心上并行執(zhí)行
一部分并發(fā)執(zhí)行,另外一部分并行執(zhí)行
在單核CPU上,如果你創(chuàng)建了1000條線程,那么CPU只會忙著上下文切換,而不會執(zhí)行實質性的任務??梢?,創(chuàng)建合理的線程數量也是一個不小的挑戰(zhàn)。
GCD 如何執(zhí)行并發(fā)/并行
GCD 在幕后管理共享線程池并在該池中添加最佳線程數。使用 GCD,您將代碼塊或工作項添加到隊列中,GCD 決定在哪個線程上執(zhí)行它們。GCD 根據系統(tǒng)物理條件或當前負載并發(fā)或并行執(zhí)行此任務。
注意:如果你給 GCD 分配兩個任務,你不確定它是并發(fā)還是并行運行。
從現(xiàn)在開始,我們將使用術語并發(fā)代表并發(fā)/并行。
使用GCD,開發(fā)者有什么責任
您所要做的就是定義要并發(fā)執(zhí)行的任務并將它們添加到適當的調度隊列中。GCD 負責創(chuàng)建所需的線程并安排您的任務在這些線程上運行,這非???/p>
調度隊列
調度隊列是一種基于 C 的組件,用于執(zhí)行自定義任務。調度隊列總是按照任務添加到隊列的順序出列和啟動任務。調度隊列是線程安全的,這意味著您可以同時從多個線程訪問它們。注意,隊列不是線程!
如果您想通過 GCD 執(zhí)行并發(fā)任務,請將它們添加到適當的調度隊列中。GCD 將基于隊列的配置,挑選并執(zhí)行任務。
串行隊列
串行調度隊列按照添加到隊列的順序一次執(zhí)行一項任務。假設您將五個任務添加到串行隊列, GCD 將從第一個任務開始,在它執(zhí)行完成之前,第二個任務將不會開始。
串行隊列通常用于同步對特定資源的訪問。假設您有兩個網絡調用都需要 10 秒,因此您決定將這兩個任務移到某些后臺線程上,而且它們都在訪問相同的資源,您想要進行一些同步,您可以將這些任務放在串行隊列中。
串行隊列串行執(zhí)行任務意味著一次只有一個線程在使用,但不能保證它們在同一線程上執(zhí)行。
您可以根據需要創(chuàng)建任意數量的串行隊列,并且每個隊列相對于所有其他隊列同時運行。換句話說,如果您創(chuàng)建四個串行隊列,則每個隊列一次僅執(zhí)行一項任務,但最多仍可以同時執(zhí)行四個任務,每個隊列一個。
如果您有兩個任務訪問相同的共享資源,但他們在不同的線程上運行,則任一線程都可以先修改資源,您需要使用鎖來確保兩個任務不會同時修改該資源。您可以將兩個任務添加到串行調度隊列,以確保在任何給定時間只有一個任務修改共享資源。這種基于隊列的同步比鎖更有效,因為在有競爭和無競爭的情況下,鎖總是需要一個昂貴的內核陷阱,而調度隊列主要在應用程序的進程空間中工作,并且只在絕對必要時調用內核。
并發(fā)隊列
并發(fā)隊列并發(fā)執(zhí)行一個或多個任務
如果您將四個單獨的任務添加到并發(fā)隊列,這些任務將按照它們添加到隊列的順序啟動。GCD 選擇第一個任務在一段時間內執(zhí)行它,然后在不等待第一個任務完成的情況下啟動第二個任務,依此類推。這是理想的地方,這不僅可以真正地做到后臺執(zhí)行,而且不關心這些任務是否也與其他任務同時運行
當前正在執(zhí)行的任務在由調度隊列管理的不同線程上運行。
在任何時間點,執(zhí)行的任務數量是可變的,這取決于系統(tǒng)條件。當您創(chuàng)建具有四個任務的并發(fā)隊列時,它會創(chuàng)建多少個線程?答案是不確定。GCD 將使用多少個線程來執(zhí)行這些任務,這取決于系統(tǒng)條件,它有可能可以使用一條或四條線程。
在 GCD 中,有兩種方法可以同時運行任務,創(chuàng)建自定義并發(fā)隊列或使用全局并發(fā)隊列。
自定義和全局并發(fā)隊列的區(qū)別
如下圖所示,我們創(chuàng)建了兩個全局并發(fā)隊列,您可以看到由于全局隊列是整個系統(tǒng)共享的并發(fā)隊列,因此它始終返回相同的隊列;而自定義并發(fā)隊列是私有的,每次創(chuàng)建時都會返回新隊列。
有四個不同優(yōu)先級的全局并發(fā)隊列,但在設置全局并發(fā)隊列時,不直接指定優(yōu)先級。
相反,您指定服務質量 (QoS),其中包括用戶交互、用戶啟動、實用程序和后臺,其中用戶交互具有最高優(yōu)先級,而后臺具有最低優(yōu)先級。下面是QoS的使用建議。
.userInteractive 標志任務需要被立即執(zhí)行以便提供更出色的用戶體驗。通常用來做UI更新,事件處理等低延時的任務。該類型的任務不應過多。
.userInitiated 標志任務被用戶通過UI界面創(chuàng)建,但是可以被異步執(zhí)行。通常用在用戶一個操作后需要等待,結果返回后繼續(xù)之前的操作。
.default 默認值。用于一般性異步任務。
.utility 標志任務需要較長時間,通常會關聯(lián)一個進度。比如:I/O、網絡請求等。
.background 標志任務的執(zhí)行用戶不太會關心。比如:預加載數據。
與全局隊列相比,您可以使用自定義隊列執(zhí)行以下任務:
您可以指定一個對您有意義的標簽,以便在自定義隊列上進行調試
你可以暫停和重啟: queue.suspend() queue.resume()
提交柵欄任務: queue.async(flags: .barrier) { ... }
主隊列
主隊列是一個全局可用的串行隊列,它在應用程序的主線程上執(zhí)行任務
該隊列與應用程序的運行循環(huán)一起工作,以將排隊任務和運行循環(huán)的其他事件源任務交錯執(zhí)行。因為它在應用程序的主線程上運行,所以主隊列通常用作應用程序的關鍵同步點。
同步與異步
我們已經學習了如何在隊列上串行或并發(fā)地執(zhí)行任務。使用 GCD,您還可以同步或異步的調度隊列。
一般來說,同步函數(sync)在任務完成后將控制權返回給調用者。而異步函數(async)會在函數調用后,立即將將控制權返回給調用者,它不會等等任務完成。
如上圖,您在并發(fā)全局隊列上執(zhí)行耗時的任務,但主線程仍然很忙,因為您在主線程 main 上同步地在分派任務,它會一直等到任務執(zhí)行完成。
如上圖:我們異步的分派任務,它立即返回到主線程,主線程將首先打印,隊列上的列任務將并發(fā)執(zhí)行。
預防死鎖
在并發(fā)計算中,死鎖是一個組的每個成員都在等待另一個成員(包括它自己)采取行動的狀態(tài)
在上圖中,queue是一個串行隊列,通過async派發(fā)的任務A,通過sync派發(fā)了任務B。此時A會等待B完成后繼續(xù)向下,而B在A沒有完成之前是不會開始。這就造成了死鎖。
同樣,上圖的主隊列任務A(viewDidLoad方法)使用sync派發(fā)任務B,也會造成相互等待從而死鎖。
DispatchWorkItem
DispatchWorkItem是一項任務的包裝器,可以多次使用,也可以取消。
letqueue=DispatchQueue(label:"com.swiftpal.dispatch.workItem") //Createaworkitem letworkItem=DispatchWorkItem(){ print("StoredTask") } //Task1 queue.async(execute:workItem) //Task2 queue.asyncAfter(deadline:DispatchTime.now()+1,execute:workItem) //WorkItemCancel workItem.cancel() //Task3 queue.async(execute:workItem) ifitem.isCancelled{ print("Taskwascancelled") }
這里我們創(chuàng)建了一個串行隊列,又創(chuàng)建了一個DispatchWorkItem,它只包含一句代碼。
接下來,我們在取消任務之前派發(fā)了該任務兩次,之后再次派發(fā)該任務。但是對于輸出,我們只會看到一次Stored task。
審核編輯:劉清
-
處理器
+關注
關注
68文章
19395瀏覽量
230659 -
控制器
+關注
關注
112文章
16432瀏覽量
178924 -
QoS
+關注
關注
1文章
136瀏覽量
44833 -
調度器
+關注
關注
0文章
98瀏覽量
5266
原文標題:Swift 并發(fā)編程一
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論