新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 編程語言的發(fā)展趨勢(shì)及未來方向(6):并發(fā)

編程語言的發(fā)展趨勢(shì)及未來方向(6):并發(fā)

作者: 時(shí)間:2017-04-17 來源:網(wǎng)絡(luò) 收藏

  這是Anders Hejlsberg(不用介紹這是誰了吧)在比利時(shí)TechDays 2010所做的開場(chǎng)演講。由于最近我在博客上關(guān)于語言的討論比較多,出于應(yīng)景,也打算將Anders的演講完整地聽寫出來。在上一部分中,Anders談?wù)摿恕霸幊獭奔八谂Φ摹熬幾g器即服務(wù)”功能。在這一部分中,Anders則談?wù)摿恕安l(fā)”,這也是他眼中發(fā)展的三種趨勢(shì)之一,并演示了.NET 4.0中并行庫(kù)的神奇效果。

本文引用地址:http://www.butianyuan.cn/article/201704/346685.htm

  如果沒有特別說明,所有的文字都直接翻譯自Anders的演講,并使用我自己的口語習(xí)慣表達(dá)出來,對(duì)于Anders的口誤及反復(fù)等情況,必要時(shí)在譯文中自然也會(huì)進(jìn)行忽略。為了方便理解,我也會(huì)將視頻中關(guān)鍵部分進(jìn)行截圖,而某些代碼演示則會(huì)直接作為文章內(nèi)容發(fā)表。

  (聽寫開始,接上篇)

    

 

  好,最后我想談的內(nèi)容是“并發(fā)”。

    

 

  聽說過摩爾定律的請(qǐng)舉手……幾乎是所有人。那么多少人聽說了摩爾定律已經(jīng)結(jié)束了呢?嗯,還是有很多人。我有好消息,也有壞消息。我認(rèn)為摩爾定律并沒有停止。摩爾定律說的是:可以在集成電路上低成本地放置晶體管的數(shù)目,約每?jī)赡瓯銜?huì)增加一倍。有趣的是,這個(gè)定律從60年代持續(xù)到現(xiàn)在,而從一些跡象上來看,這個(gè)定律會(huì)繼續(xù)保持20到30年。

  摩爾定理有個(gè)推論,便是說時(shí)鐘速度將根據(jù)相同的周期提高,也就是說每隔大約24個(gè)月,CPU的速度便會(huì)加倍──而這點(diǎn)已經(jīng)停止了。再來統(tǒng)計(jì)一下,你們之中有誰的機(jī)器里有20GHz的CPU?看到了沒?一個(gè)人都沒有。但如果你從五年前開始計(jì)算的話,現(xiàn)在我們應(yīng)該已經(jīng)在使用20GHz的CPU了,但事實(shí)并非如此。這點(diǎn)在五年前就停止了,而且事實(shí)上最大速度還有些下降,因?yàn)榘l(fā)熱量實(shí)在太大了,會(huì)消耗許多能源,讓電池用的太快。

    

 

  有些物理方面的基礎(chǔ)因素讓CPU不能運(yùn)行的太快。然而,另一意義上的摩爾定理出現(xiàn)了。我們還是可以看到容量的增加,因?yàn)榭梢栽谕粋€(gè)表盤上放置多個(gè)CPU了。目前已經(jīng)有了雙核、四核,Intel的CTO在三年前說,十年后我們可以出現(xiàn)80核的處理器。

    

 

  到了那個(gè)時(shí)候,你的任務(wù)管理器中就可能是這樣的。似乎有些嚇人,不過這是我們實(shí)驗(yàn)室中真實(shí)存在的128核機(jī)器。你可以看到,計(jì)算能力已經(jīng)完全用上了。這便是個(gè)問題,比如你在這臺(tái)強(qiáng)大的機(jī)器上進(jìn)行一個(gè)實(shí)驗(yàn),你自然希望看到100%的使用狀況,不過傳統(tǒng)的實(shí)驗(yàn)都是在一個(gè)核上執(zhí)行的,所以我們面臨的挑戰(zhàn)是,我們需要換一種寫程序的方式來利用此類機(jī)器。

  我的一個(gè)同事,Herb Sutter,他寫過一篇文章,談到“免費(fèi)的午餐已經(jīng)結(jié)束了”。沒錯(cuò),我們已經(jīng)不能寫一個(gè)程序,然后對(duì)客戶說:啊,未來的硬件會(huì)讓它運(yùn)行的越來越快,我們不用關(guān)心太多──不,已經(jīng)不會(huì)這樣了,除非你換種不同的寫法。實(shí)話說,這是個(gè)挑戰(zhàn),也是個(gè)機(jī)遇。說它是個(gè)挑戰(zhàn),是因?yàn)椴l(fā)十分困難,至今我們對(duì)此還沒有簡(jiǎn)單的答案,稍后我會(huì)演示一些正有所改善的東西,但……這也是一個(gè)機(jī)遇,在這樣的機(jī)器上,你的確可以用完所有的核,這樣便能獲得性能提高,不過做法需要有所不同。

  多核革命的一個(gè)有趣之處在于,它對(duì)于并發(fā)的思維方式會(huì)有所改變。傳統(tǒng)的并發(fā)思維是在單個(gè)CPU上執(zhí)行多個(gè)邏輯任務(wù),使用舊有的分時(shí)方式、時(shí)間片模型來執(zhí)行多個(gè)任務(wù)。但是,你想一下便會(huì)發(fā)現(xiàn)如今的并發(fā)情況正好相反,現(xiàn)在是要將一個(gè)邏輯上的任務(wù)放在多個(gè)CPU上執(zhí)行。這改變了我們編寫程序的方式,這意味著對(duì)于語言或是API來說,我們需要有辦法來分解任務(wù),把它拆分成多個(gè)小任務(wù)后獨(dú)立的執(zhí)行,而傳統(tǒng)的中并不關(guān)注這點(diǎn)。

    

 

  使用目前的并發(fā)API來完成工作并不容易,比如使用Thread,ThreadPool,lock,Monitor等等,你無法太好的進(jìn)展。不過.NET 4.0提供了一些美妙的事物,我們稱之為.NET并行擴(kuò)展。它是一種現(xiàn)代的并發(fā)模型,將邏輯上的任務(wù)并發(fā)與我們實(shí)際使用的的物理模型分離開來。以前我們的API都是直接處理線程,也就是(上圖)下方橙色的部分,不過有了.NET并行擴(kuò)展之后,你可以使用更為邏輯化的編程風(fēng)格。任務(wù)并行庫(kù)(Task Parallel Library),并行LINQ(Parallel LINQ)以及協(xié)調(diào)數(shù)據(jù)結(jié)構(gòu)(Coordination Data Structures)讓你可以直接關(guān)注邏輯上的任務(wù),而不必關(guān)心它們是如何運(yùn)行的,或是使用了多少個(gè)線程和CPU等等。

    

 

  下面我來簡(jiǎn)單演示一下它們的使用方式。我?guī)砹艘粋€(gè)PLINQ演示,這里是一些代碼,讀取XML文件的內(nèi)容。這有個(gè)50M大小的popname.xml文件,保存了美國(guó)社會(huì)安全數(shù)據(jù)庫(kù)里的信息,包含某個(gè)洲在某一年的人口統(tǒng)計(jì)信息。這個(gè)程序會(huì)讀取這個(gè)XML文件,把它轉(zhuǎn)化成一系列對(duì)象,并存放在一個(gè)List中。然后對(duì)其執(zhí)行一個(gè)LINQ語句,查找所有在華盛頓名叫Robert的人,再根據(jù)年份進(jìn)行排序:

  Console.WriteLine("Loading XML data...");

  var popNames =

  (from e in XElement.Load("popnames.xml").Elements("Name")

  select new

  {

  Name = (string)e.Attribute("Name"),

  State = (string)e.Attribute("State"),

  Year = (int)e.Attribute("Year"),

  Count = (int)e.Attribute("Count")

  })

  .ToList();

  Console.WriteLine(popNames.Count + " records");

  Console.WriteLine();

  string targetName = "Robert";

  string targetState = "WA";

  var querySequential =

  from n in popNames

  where n.Name == targetName && n.State == targetState

  orderby n.Year

  select n;

  我們來執(zhí)行一下……首先加載XML文件,然后進(jìn)行查詢。利用PLINQ我們可以做到并行地查詢。我們只要拷貝一份代碼……改成queryParallel……現(xiàn)在我唯一要做的只是在數(shù)據(jù)源上使用AsParallel擴(kuò)展方法,這樣便會(huì)引入一套新的類型和實(shí)現(xiàn),此時(shí)相同的LINQ操作使用的便是并行的實(shí)現(xiàn):

  var queryParallel =

  from n in popNames.AsParallel()

  where n.Name == targetName && n.State == targetState

  orderby n.Year

  select n;

  我們重新執(zhí)行兩個(gè)查詢。

    

 

  再次加載XML數(shù)據(jù)……并行實(shí)現(xiàn)使用了1.5秒,我們?cè)僭囍\(yùn)行一次,一般結(jié)果會(huì)更好一些,現(xiàn)在可能剛好在執(zhí)行一些后臺(tái)任務(wù)。一般我們可以得到更快的結(jié)果……這次比較接近了?,F(xiàn)在你可以觀察到,我們并不需要做太多事情,便可以在我的雙核機(jī)器上得到并發(fā)的效果。

    

 

  這里我無法保證說,我們只要隨時(shí)加上AsParallel便可以得到兩倍的性能,有時(shí)可以有時(shí)不行,有些查詢能夠被并行,有的則不可以。然而,我想你一定同意一點(diǎn),使用如LINQ這樣的DSL能夠方便我們編寫并行的代碼,也更有可能利用起并行效果。雖然不是每次都有效,但是嘗試的成本也很低。如果我們使用普通的for循環(huán)來編寫代碼,在某個(gè)地方使用線程池等等,便很容易在這些API里失去方向。而這里我們只要簡(jiǎn)單地嘗試一下,便能知道是否可以提高性能了。

    

 

  這里你已經(jīng)看到我使用的LINQ查詢,而現(xiàn)在也有很多工作是通過循環(huán)來完成的。你可以想象主要的運(yùn)算是從哪里來的,很自然會(huì)是在循環(huán)里操作數(shù)據(jù)。如果循環(huán)的每個(gè)迭代都是獨(dú)立的,便有很大的機(jī)會(huì)可以利用并發(fā)操作──我知道這里是“如果”,不過長(zhǎng)期來看則一定會(huì)出現(xiàn)這樣的情況。這時(shí)候便可以使用并行擴(kuò)展,或者說是.NET并行擴(kuò)展里的新API,把循環(huán)轉(zhuǎn)化成并行的循環(huán),只要簡(jiǎn)單的改變……幾乎只要用同樣的循環(huán)體把for重構(gòu)成Parallel.For就行了。如果你有foreach操作就可以使用Parallel.ForEach,或是一系列順序執(zhí)行的語句也可以用上Parallel.Invoke。此時(shí)任務(wù)并行庫(kù)會(huì)接管并執(zhí)行這些任務(wù),根據(jù)你的CPU數(shù)量使用最優(yōu)化的線程數(shù)量,你不需要關(guān)注更深的細(xì)節(jié),只需要編寫邏輯就可以了。

    

 

  就像我說的那樣,可能你會(huì)有獨(dú)立的任務(wù)但也可能沒有,所以很多時(shí)候我們需要來關(guān)注這方面的事情。比如“隔離性(Isolation)”。例如,編譯器如何發(fā)現(xiàn)這段代碼是獨(dú)立的,可以安全地并發(fā)執(zhí)行,好比我創(chuàng)建了一個(gè)對(duì)象,在分享給其他人之前,我對(duì)它的改變是安全的。但是我一旦把它們共享出去了,那么它們便不安全了。所以如果我們的類型系統(tǒng)可以跟蹤到這樣的共享,如Linear Types──這在學(xué)術(shù)界也有一些研究。我們也可以在函數(shù)的純潔性(Purity)方面下功夫,如關(guān)注某個(gè)函數(shù)是否有副作用,有些時(shí)候編譯器可以做這方面的檢查,它可以禁止某些操作,以此保證我們寫出純函數(shù)。還有便是不可變性(Immutability),目前的C#或VB,我們需要額外的工作才能寫出不可變的代碼──但本不該這樣,我們應(yīng)該在語言層面上更好的支持不可變性。這些都是在并發(fā)方面需要考慮的問題。

  如果說有哪個(gè)語言特性超出這個(gè)范疇,我想說這里還有一個(gè)原則:你不該期望C#中出現(xiàn)某個(gè)特別的并發(fā)模型,而應(yīng)該是一種通用的,可用于各種不同的并發(fā)場(chǎng)景的特性,就像隔離性、純潔性及不可變性那樣。語言擁有這樣的特性之后,就可以用于構(gòu)建各種不同的API,各種并發(fā)方式都可以利用到核心的語言特性。

  (未完待續(xù))



關(guān)鍵詞: 編程語言

評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉