7.單線程并發(fā)

●?經(jīng)典多線程架構(gòu)

●?單線程/同線程架構(gòu)

●?單線程架構(gòu)的挑戰(zhàn)

●?線程循環(huán)

????○?暫停線程循環(huán)

●?兩種類型的任務(wù)

????○?重復(fù)任務(wù)

????○?一次性任務(wù)

●?單線程任務(wù)切換

????○?重復(fù)任務(wù)之間的任務(wù)切換

????○?一次性任務(wù)之間的任務(wù)切換

●?合并重復(fù)任務(wù)和一次性任務(wù)

●?任務(wù)平衡

????○?優(yōu)先執(zhí)行

????○?任務(wù)停泊

●?單線程并發(fā)的擴展


單線程并發(fā)意味著貌似可以在單個線程中同時完成多個任務(wù)。 從表面上看,單線程并發(fā)聽起來有點矛盾。 以前,在多線程體系結(jié)構(gòu)中,多個任務(wù)將在多個線程之間分配,以并行執(zhí)行。 因此,不同任務(wù)之間的切換是通過操作系統(tǒng)和CPU在不同線程之間的切換來完成的。 但是,單個線程實際上可以幾乎同時處理多個任務(wù)。 在本單線程并發(fā)教程中,我將解釋單線程并發(fā)如何設(shè)計的,以及有何好處。請注意:本教程仍在進行中。 在不久的將來會添加更多!

請注意:本教程仍在進行中。 在不久的將來會添加更多!

經(jīng)典多線程架構(gòu)

在經(jīng)典的多線程體系架構(gòu)中,通常將每個任務(wù)分配給一個單獨的線程以執(zhí)行。 每個線程一次只執(zhí)行一個任務(wù)。 在某些設(shè)計中,將為每個任務(wù)創(chuàng)建一個新線程,因此一旦任務(wù)完成,該線程就會死掉。 在其他設(shè)計中,線程池保持活動狀態(tài),該線程池一次從任務(wù)隊列中執(zhí)行一個任務(wù),然后執(zhí)行另一任務(wù),如此往復(fù)。有關(guān)更多信息,請參閱我的線程池教程。

多線程體系架構(gòu)的優(yōu)點是,相對容易地在多個線程和多個CPU之間分配工作負載。 只需將任務(wù)分配給線程,然后讓OS / CPU將線程調(diào)度到CPU。

但是,如果正在執(zhí)行的任務(wù)需要共享數(shù)據(jù),則多線程體系架構(gòu)可能會導(dǎo)致許多并發(fā)問題,例如競態(tài)條件,死鎖,饑餓,滑動條件,嵌套監(jiān)視器鎖定等。通常,越多的線程共享相同的數(shù)據(jù)和數(shù)據(jù)結(jié)構(gòu),發(fā)生并發(fā)問題的可能性就越高。 換句話說,您需要在設(shè)計時留意更多內(nèi)容。

當(dāng)多個線程試圖同時訪問同一個數(shù)據(jù)結(jié)構(gòu)時,經(jīng)典的多線程體系結(jié)構(gòu)有時還會導(dǎo)致?lián)砣?這取決于給定數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)方式,某些線程可能會被阻塞,以等待其他正在訪問該數(shù)據(jù)結(jié)構(gòu)線程訪問完成。

單線程/同線程架構(gòu)

經(jīng)典多線程體系結(jié)構(gòu)的替代方法是單線程或同線程。 通過僅使用一個線程來執(zhí)行應(yīng)用程序中的所有任務(wù),就可以完全避免前一部分(經(jīng)典的多線程并發(fā)架構(gòu))中列出的所有并發(fā)問題。

您可以擴展單線程體系結(jié)構(gòu)以使用多個線程,其中每個線程的行為就像是一個單獨的隔離的單線程系統(tǒng)。 在那種情況下,我將此架構(gòu)稱為相同線程。 執(zhí)行任務(wù)所需的所有數(shù)據(jù)仍保持隔離在單個線程內(nèi)-在同一線程內(nèi)。

單線程架構(gòu)的挑戰(zhàn)

如果只有一個線程執(zhí)行應(yīng)用程序的所有任務(wù),則可能會導(dǎo)致一些問題:

????●?從任務(wù)中阻止IO操作,將阻止線程,從而阻止整個應(yīng)用程序。

????●?長時間運行的任務(wù),可能會產(chǎn)生無法接受的延遲其他任務(wù)的執(zhí)行。

????●?單個線程只能使用到單個CPU。

可以解決這些問題,但又不會失去單線程并發(fā)體系結(jié)構(gòu)的簡單性的優(yōu)勢,也不會使整體設(shè)計過于復(fù)雜。

線程循環(huán)

大多數(shù)長時間運行的應(yīng)用程序以某種循環(huán)執(zhí)行,其中應(yīng)用程序主線程正在等待來自應(yīng)用程序外部的輸入,處理該輸入,然后返回等待狀態(tài)。

線程循環(huán)

這種線程循環(huán)在服務(wù)器應(yīng)用程序(Web服務(wù),服務(wù)等)和GUI應(yīng)用程序中都可以使用。 有時,您可以看到該線程循環(huán),?而有時則看不到。

暫停線程循環(huán)

您可能會想知道,在一遍又一遍地密集循環(huán)中,執(zhí)行的線程是否會浪費大量CPU時間。 如果線程在運行時沒有任何實際工作要做,那么可能會浪費掉大量CPU時間。?因此,如果執(zhí)行循環(huán)的線程判斷出休眠幾毫秒是可行的,則可能會使用“休眠”,而非循環(huán), 這樣可以減少CPU的時間浪費。

兩種類型的任務(wù)

線程循環(huán)通常在其生命周期內(nèi)執(zhí)行兩種類型的任務(wù):

????●?重復(fù)任務(wù)

????●?一次性任務(wù)

以下各節(jié)將對這兩項任務(wù)進行更詳細的說明。

重復(fù)任務(wù)

重復(fù)任務(wù)是一個重復(fù)執(zhí)行的任務(wù),它在執(zhí)行該任務(wù)的線程的生命周期內(nèi)一次又一次地執(zhí)行。 通常,對于任務(wù)的每次調(diào)用,將完全執(zhí)行重復(fù)的任務(wù)。

重復(fù)任務(wù)的一個示例是檢查一組入站網(wǎng)絡(luò)連接上的傳入數(shù)據(jù)。 如果檢測到任何傳入數(shù)據(jù),將對其進行處理,并且在處理之后,將針對此特定調(diào)用執(zhí)行重復(fù)的任務(wù)。 但是,需要一次又一次地檢查入站數(shù)據(jù),以使應(yīng)用程序能夠連續(xù)響應(yīng)傳入的數(shù)據(jù)。

重復(fù)任務(wù)

一次性任務(wù)

一次性任務(wù)是只需要執(zhí)行一次的任務(wù)。一次性任務(wù)可以是短期運行,也可以是長期運行。

短時任務(wù)是一個足夠短的任務(wù),可以在一個執(zhí)行階段中完成,而又不會使執(zhí)行該任務(wù)的線程因該線程承擔(dān)的其他職責(zé)(它必須執(zhí)行的其他任務(wù))而延遲。

一次性長時間運行的任務(wù)是在單個執(zhí)行階段中花費太長時間才能完成的任務(wù)。 “花費太長時間”是指執(zhí)行任務(wù)中的全部工作量將占用太多的線程時間,因此其他重復(fù)任務(wù)或一次性任務(wù)將被延遲太多,以至于應(yīng)用程序的總響應(yīng)速度受到傷害。

為了避免單個長時間運行的任務(wù)占用過多的線程執(zhí)行時間,將完成任務(wù)所需的全部工作分解為較小的塊,可以一次執(zhí)行一個塊。每個塊必須足夠小,以免延遲線程執(zhí)行過多任務(wù)所需的其他任務(wù)。

長時間運行的任務(wù)在內(nèi)部跟蹤其執(zhí)行塊。執(zhí)行長時間運行的任務(wù)的線程將多次調(diào)用其執(zhí)行方法,直到所有任務(wù)塊均已完全執(zhí)行。在調(diào)用特定長時間運行任務(wù)的執(zhí)行方法之間,線程可以調(diào)用其他長時間運行任務(wù),其他重復(fù)任務(wù)或線程承擔(dān)的任何職責(zé)的執(zhí)行方法。

一次性的長期運行任務(wù)可能是處理目錄中的N個文件??梢詫個文件的處理分解成較小的塊,而不是在單個執(zhí)行階段中處理所有N個文件,而每個塊都在單個執(zhí)行階段中進行處理。例如,每個執(zhí)行階段可以處理1個文件。要處理所有N個文件的任務(wù)

在線程循環(huán)中,一次性任務(wù)通常由重復(fù)任務(wù)檢測并執(zhí)行,如下所示。

一次性任務(wù)

單線程任務(wù)切換

為了能夠似乎同時在一個以上的任務(wù)上取得進展,在任務(wù)上取得進展的線程必須能夠在這些任務(wù)之間進行切換。 這也稱為任務(wù)切換。

任務(wù)切換的確切工作方式取決于任務(wù)的類型-線程是在重復(fù)任務(wù)還是一次性任務(wù)之間進行切換。 雖然總的原理還是一樣的。 我將在以下各節(jié)中對這兩者進行更詳細的說明。

重復(fù)任務(wù)之間的任務(wù)切換

重復(fù)的任務(wù)通常只有一個方法,該方法被同一線程重復(fù)調(diào)用。 重復(fù)任務(wù)是應(yīng)在應(yīng)用程序的整個生命周期中重復(fù)的任務(wù),因此它永遠不會真正“完成”。 重復(fù)的任務(wù)執(zhí)行了所需的操作,然后退出其執(zhí)行方法,將控制權(quán)交還給調(diào)用線程。

通過以循環(huán)方式調(diào)用它們的執(zhí)行方法,單個線程可以在多個重復(fù)任務(wù)之間進行切換。 首先重復(fù)執(zhí)行的任務(wù)A有執(zhí)行的機會,然后是B,然后是C,然后是A,依此類推。

萬一重復(fù)任務(wù)沒有完全完成它開始的任何工作,它可以記錄它在內(nèi)部走了多遠,并在下次調(diào)用重復(fù)任務(wù)時從那里繼續(xù)。

重復(fù)任務(wù)之間的任務(wù)切換

一次性任務(wù)之間的任務(wù)切換

一次性任務(wù)與重復(fù)任務(wù)的不同之處在于,一次性任務(wù)有望在某個時間點完成。 這意味著,有時需要從任務(wù)池中刪除一次性任務(wù)。

除此之外,一次完成任務(wù)之間的切換類似于重復(fù)任務(wù)之間的切換。 執(zhí)行線程調(diào)用給定的一次性任務(wù)的執(zhí)行方法,該任務(wù)在短時間內(nèi)取得進展,然后在內(nèi)部記錄其執(zhí)行的距離,然后退出其執(zhí)行方法,將控制權(quán)交還給調(diào)用線程。 現(xiàn)在,調(diào)用線程可以循環(huán)方式調(diào)用任務(wù)池中的下一個一次性任務(wù)。

每次調(diào)用一次性任務(wù)的執(zhí)行方法后,調(diào)用線程將檢查任務(wù)是否已完成。 如果已刪除,則一次性任務(wù)將從任務(wù)池中刪除。

一次性任務(wù)之間的任務(wù)切換

合并重復(fù)任務(wù)和一次性任務(wù)

在實踐中,一個應(yīng)用程序可能包含一個調(diào)用一個或多個重復(fù)任務(wù)的線程循環(huán),重復(fù)任務(wù)可以執(zhí)行一次任務(wù)作為重復(fù)行為的一部分。 下圖說明了這一點。 該圖僅描述了一個重復(fù)的任務(wù),但根據(jù)具體應(yīng)用,可能還會有更多任務(wù)。

合并重復(fù)任務(wù)和一次性任務(wù)

任務(wù)平衡

當(dāng)單個線程要在多個任務(wù)(無論是重復(fù)任務(wù)還是一次性任務(wù))之間切換時,必須確保在一次調(diào)用任務(wù)時,這些任務(wù)不會占用過多的線程執(zhí)行時間。換句話說,確保每個任務(wù)之間執(zhí)行時間的公平平衡是每個任務(wù)幫助的職責(zé)。

任務(wù)應(yīng)該允許自己執(zhí)行多長時間,具體取決于系統(tǒng)設(shè)計者。對于一次性任務(wù),這可能會有些復(fù)雜。有些任務(wù)自然很快就完成了,而另一些任務(wù)自然要花費更長的時間才能完成。對于運行時間較長的任務(wù),由任務(wù)的實現(xiàn)者來估計如何將工作分解為足夠小的分區(qū),以便可以在不延遲其他任務(wù)過多的情況下執(zhí)行每個分區(qū)。

需要注意的一件有趣的事是,如果線程以循環(huán)方式調(diào)用每個一次性任務(wù),那么任務(wù)執(zhí)行器包含的一次性任務(wù)越多,每個線程獲得的執(zhí)行時間就越短,因為在執(zhí)行任務(wù)之前需要更長的時間接下來的執(zhí)行時間。

優(yōu)先執(zhí)行

可以實現(xiàn)一個將某些任務(wù)優(yōu)先于其他任務(wù)的任務(wù)執(zhí)行器。 例如,任務(wù)執(zhí)行者可以在內(nèi)部將任務(wù)保存在不同的列表中,例如 執(zhí)行低優(yōu)先級任務(wù)列表中的任務(wù)每執(zhí)行1次,就執(zhí)行2次高優(yōu)先級列表中的任務(wù)。

確切地說,如何執(zhí)行優(yōu)先任務(wù)執(zhí)行器將取決于具體需求。 還有多少個優(yōu)先級,例如 低/高,或低/中/高等

任務(wù)停泊

如果一次性任務(wù)正在等待某些異步操作完成,例如 如果來自遠程服務(wù)器的答復(fù),則一次性任務(wù)將無法繼續(xù)進行下去,直到它正在等待的異步操作完成為止。 在那種情況下,一次又一次地調(diào)用該任務(wù)可能沒有意義,只是為了使該任務(wù)意識到它無法取得任何進展并立即將控制權(quán)返回給調(diào)用線程。

在這種情況下,一次性任務(wù)能夠?qū)⒆约骸巴7拧痹谌蝿?wù)執(zhí)行器內(nèi)部可能是有意義的,因此不再被調(diào)用。 異步操作完成后,一次性任務(wù)可以取消停放,然后重新插入到活動任務(wù)中,這些活動將連續(xù)調(diào)用以取得進展。 當(dāng)然,要能夠取消任務(wù),系統(tǒng)的其他部分必須檢測到異步操作已完成,以及要為該異步操作取消任務(wù)。

單線程并發(fā)的擴展

顯然,如果在應(yīng)用程序中只有一個線程正在執(zhí)行,則不能利用多個CPU。 解決方案是啟動多個線程。 通常,每個CPU一個線程-取決于您的線程需要執(zhí)行哪種任務(wù)。 如果您有需要執(zhí)行阻塞IO工作的任務(wù),例如從文件系統(tǒng)或網(wǎng)絡(luò)中讀取數(shù)據(jù),則每個CPU可能需要多個線程。 每個線程將在等待阻塞的IO操作完成時被阻塞,不執(zhí)行任何操作。

單線程并發(fā)的擴展

當(dāng)您將單線程體系結(jié)構(gòu)擴展到多個單線程子系統(tǒng)時,從技術(shù)上講,它不再是單線程的。 但是,每個單線程子系統(tǒng)通常都將被設(shè)計為一個單線程系統(tǒng),并表現(xiàn)為一個單線程系統(tǒng)。 我曾經(jīng)將這樣的多線程單線程系統(tǒng)稱為同線程系統(tǒng),盡管我不確定這實際上是最精確的術(shù)語。 我們可能需要重新審視這些不同的設(shè)計,并在將來為它們提供更具描述性的術(shù)語。


譯自:Singlethreaded Concurrency

Jakob Jenkov

Last update: 2020-12-11

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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