1.5 使用輕量級(jí)并發(fā)模型
Amdahl法則和Gustafson法則都沒有考慮引入并行所帶來的開銷。這兩個(gè)法則也沒有考慮到存在一些模式能夠?qū)⒋胁糠洲D(zhuǎn)換為能夠充分利用并行化的新算法。減少應(yīng)用程序中串行部分的代碼對(duì)于提高并行執(zhí)行單元的使用率而言非常重要。
在以前版本的.NET Framework中,如果想要在C#應(yīng)用程序(一個(gè)進(jìn)程)中并行地運(yùn)行代碼,那么您必須創(chuàng)建并管理多個(gè)線程(軟件線程)。因此,您必須編寫非常復(fù)雜的多線程代碼。將算法分解為多個(gè)線程、協(xié)調(diào)各個(gè)代碼單元、在代碼單元之間共享信息以及收集運(yùn)算結(jié)果等任務(wù)實(shí)在是非常復(fù)雜的程序設(shè)計(jì)工作。隨著邏輯內(nèi)核的增加,這個(gè)任務(wù)會(huì)變得更加復(fù)雜,因?yàn)槟枰ㄟ^更多線程來獲得更好的可擴(kuò)展性。
多線程編程模型的設(shè)計(jì)并不是為了幫助開發(fā)人員面對(duì)多核革命。事實(shí)上,創(chuàng)建新的線程需要執(zhí)行大量的處理器指令,而且可能會(huì)對(duì)已經(jīng)分解為并行化線程的代碼引入太大的開銷。很多有用的數(shù)據(jù)結(jié)構(gòu)和類在設(shè)計(jì)上并沒有考慮到被多線程訪問,因此,為了能夠?qū)崿F(xiàn)這一點(diǎn)還需要添加很多代碼。這些額外的代碼會(huì)使得開發(fā)人員的注意力偏離主要目標(biāo):通過并行執(zhí)行來提升性能。
由于這個(gè)多線程模型過于復(fù)雜,難以應(yīng)對(duì)多核革命,因此這個(gè)模型被稱為重量級(jí)并發(fā)(heavyweight concurrency)。這個(gè)模型加入了嚴(yán)重的開銷,需要編寫很多代碼來處理由于框架層次缺乏對(duì)多線程訪問的支持而帶來的問題,并且會(huì)導(dǎo)致代碼復(fù)雜難以理解。
由于以前版本的.NET Framework所提供的多線程模型和現(xiàn)代微處理器日益增長的邏輯內(nèi)核數(shù)引發(fā)的上述問題,促進(jìn)了允許創(chuàng)建并行化代碼的新模型的創(chuàng)建。這個(gè)新模型稱為輕量級(jí)并發(fā)(lightweight concurrency),這個(gè)模型減少了在不同邏輯內(nèi)核上創(chuàng)建和執(zhí)行代碼所需要的總開銷。這并不是說能夠完全消除并行化帶來的開銷,但是這個(gè)模型本身是為現(xiàn)代多核微處理器而設(shè)計(jì)的。重量級(jí)并發(fā)模型是在多處理器的時(shí)代出現(xiàn)的,在那個(gè)時(shí)代計(jì)算機(jī)可能有很多物理微處理器,每個(gè)微處理器只有一個(gè)內(nèi)核。輕量級(jí)的并發(fā)模型考慮了新的微架構(gòu),這個(gè)架構(gòu)中有很多由一些物理內(nèi)核支撐的邏輯內(nèi)核。
輕量級(jí)并發(fā)模型并不只是關(guān)注不同邏輯內(nèi)核之間的作業(yè)調(diào)度,它還在框架級(jí)別添加了對(duì)多線程訪問的支持,從而使得代碼更容易理解。
大多數(shù)現(xiàn)代程序設(shè)計(jì)語言都在向輕量級(jí)并發(fā)模型變革,幸運(yùn)的是,.NET Framework 4也加入了這一波變革。因此,所有能夠生成.NET應(yīng)用程序的托管語言都能夠充分利用這種新的模型并發(fā)揮其優(yōu)勢。
1.6 創(chuàng)建成功的基于任務(wù)的設(shè)計(jì)
有時(shí)候,必須對(duì)現(xiàn)有的解決方案進(jìn)行優(yōu)化才能夠充分利用并行化的優(yōu)勢。在這些情況下,您必須理解現(xiàn)有的串行設(shè)計(jì),或者理解提供了有限可擴(kuò)展性的并行化算法,然后再對(duì)現(xiàn)有設(shè)計(jì)進(jìn)行重構(gòu),從而使其獲得性能提升,而且不會(huì)引入問題或產(chǎn)生不同的結(jié)果。您既可以取問題的一小部分,或者也可以考慮整個(gè)問題,并創(chuàng)建出一個(gè)基于任務(wù)的設(shè)計(jì),然后就可以引入并行化了。在設(shè)計(jì)新解決方案的時(shí)候也可以采取同樣的技術(shù)。
遵循以下的步驟就可以創(chuàng)建出成功的基于任務(wù)的設(shè)計(jì):
(1) 將每個(gè)問題分解為很多子問題,完全不要去考慮順序執(zhí)行。
(2) 將每個(gè)子問題想象為下面三類中的一類:
● 能夠以并行的方式進(jìn)行處理的數(shù)據(jù)——對(duì)數(shù)據(jù)進(jìn)行分解以實(shí)現(xiàn)并行化。
● 需要很多任務(wù),而且能夠以某種復(fù)雜的并行化進(jìn)行處理的數(shù)據(jù)流——對(duì)數(shù)據(jù)和任務(wù)進(jìn)行分解以實(shí)現(xiàn)并行化。
● 可以并行運(yùn)行的任務(wù)——對(duì)任務(wù)進(jìn)行分解以實(shí)現(xiàn)并行化。
(3) 將設(shè)計(jì)組織為能夠表達(dá)并行化的形式。
(4) 考慮將不同子問題連接起來的任務(wù)的必要性。盡可能地避免依賴性。
(5) 在進(jìn)行設(shè)計(jì)的時(shí)候,心里要想著并發(fā)和潛在的并行化。
(6) 分析并行化的問題的執(zhí)行計(jì)劃,考慮當(dāng)前的多核微處理器和未來的架構(gòu)。在設(shè)計(jì)的時(shí)候要準(zhǔn)備好更高的可擴(kuò)展性。
(7) 盡可能減少臨界區(qū)。
(8) 盡可能通過基于任務(wù)的程序設(shè)計(jì)實(shí)現(xiàn)并行化。
(9) 調(diào)優(yōu)和迭代。
上述步驟并不是說所有的子問題都必須是運(yùn)行在不同線程中的并行化任務(wù)。設(shè)計(jì)的時(shí)候必須考慮并行化的可能性,然后在編寫代碼的時(shí)候,可以根據(jù)性能和可擴(kuò)展性的目標(biāo)選擇最佳的實(shí)現(xiàn)方式。重要的是要以并行方式進(jìn)行思考,將要解決的工作分解為任務(wù)。通過這種方式,您就能夠按照需要對(duì)代碼進(jìn)行并行化。如果您已經(jīng)擁有一個(gè)面向傳統(tǒng)串行執(zhí)行的設(shè)計(jì),那么您將需要努力通過基于任務(wù)的程序設(shè)計(jì)技術(shù)對(duì)現(xiàn)有設(shè)計(jì)進(jìn)行并行化。