新聞中心

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

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

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

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

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

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

  (聽寫開始,接上篇)

    

 

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

    

 

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

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

    

 

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

    

 

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

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

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

    

 

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

    

 

  下面我來簡單演示一下它們的使用方式。我?guī)砹艘粋€PLINQ演示,這里是一些代碼,讀取XML文件的內(nèi)容。這有個50M大小的popname.xml文件,保存了美國社會安全數(shù)據(jù)庫里的信息,包含某個洲在某一年的人口統(tǒng)計信息。這個程序會讀取這個XML文件,把它轉(zhuǎn)化成一系列對象,并存放在一個List中。然后對其執(zhí)行一個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擴展方法,這樣便會引入一套新的類型和實現(xiàn),此時相同的LINQ操作使用的便是并行的實現(xiàn):

  var queryParallel =

  from n in popNames.AsParallel()

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

  orderby n.Year

  select n;

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

    

 

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

    

 

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

    

 

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

    

 

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

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

  (未完待續(xù))



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

評論


相關(guān)推薦

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

關(guān)閉