这是Anders Hejlsberg(不用介绍这是谁了吧)在比利时TechDays 2010所做的开场讲演。由于最近我在博客上关于言语的评论比较多,出于应景,也计划将Anders的讲演完整地听写出来。在上一部分中,Anders议论了“元编程”及他正在尽力的“编译器即服务”功用。在这一部分中,Anders则议论了“并发”,这也是他眼中编程言语开展的三种趋势之一,并演示了.NET 4.0中并行库的奇特作用。
假如没有特别阐明,一切的文字都直接翻译自Anders的讲演,并运用我自己的白话习气表达出来,关于Anders的口误及重复等状况,必要时在译文中天然也会进行疏忽。为了便利了解,我也会将视频中要害部分进行截图,而某些代码演示则会直接作为文章内容宣布。
(听写开端,接上篇)
好,最终我想谈的内容是“并发”。
听说过摩尔规律的请举手……简直是一切人。那么多少人听说了摩尔规律现已完毕了呢?嗯,仍是有许多人。我有好消息,也有坏消息。我以为摩尔规律并没有中止。摩尔规律说的是:能够在%&&&&&%上低本钱地放置晶体管的数目,约每两年便会添加一倍。风趣的是,这个规律从60年代持续到现在,而从一些痕迹上来看,这个规律会持续坚持20到30年。
摩尔定理有个推论,就是说时钟速度将依据相同的周期进步,也就是说每隔大约24个月,CPU的速度便会加倍──而这点现已中止了。再来核算一下,你们之中有谁的机器里有20GHz的CPU?看到了没?一个人都没有。但假如你从五年前开端核算的话,现在咱们应该现已在运用20GHz的CPU了,但现实并非如此。这点在五年前就中止了,并且现实上最大速度还有些下降,由于发热量实在太大了,会耗费许多动力,让电池用的太快。
有些物理方面的根底要素让CPU不能运转的太快。可是,另一意义上的摩尔定理呈现了。咱们仍是能够看到容量的添加,由于能够在同一个表盘上放置多个CPU了。现在现已有了双核、四核,Intel的CTO在三年前说,十年后咱们能够呈现80核的处理器。
到了那个时分,你的使命管理器中就或许是这样的。好像有些吓人,不过这是咱们试验室中实在存在的128核机器。你能够看到,核算才干现已彻底用上了。这就是个问题,比方你在这台强壮的机器上进行一个试验,你天然希望看到100%的运用状况,不过传统的试验都是在一个核上履行的,所以咱们面对的应战是,咱们需求换一种写程序的办法来运用此类机器。
我的一个搭档,Herb Sutter,他写过一篇文章,谈到“免费的午饭现已完毕了”。没错,咱们现已不能写一个程序,然后对客户说:啊,未来的硬件会让它运转的越来越快,咱们不用关怀太多──不,现已不会这样了,除非你换种不同的写法。实话说,这是个应战,也是个时机。说它是个应战,是由于并发好不简略,至今咱们对此还没有简略的答案,稍后我会演示一些正有所改善的东西,但……这也是一个时机,在这样的机器上,你确实能够用完一切的核,这样便能取得功能进步,不过做法需求有所不同。
多核革新的一个风趣之处在于,它关于并发的思想办法会有所改动。传统的并发思想是在单个CPU上履行多个逻辑使命,运用旧有的分时办法、时刻片模型来履行多个使命。可是,你想一下便会发现现在的并发状况正好相反,现在是要将一个逻辑上的使命放在多个CPU上履行。这改动了咱们编写程序的办法,这意味着关于言语或是API来说,咱们需求有办法来分化使命,把它拆分红多个小使命后独立的履行,而传统的编程言语中并不重视这点。
运用现在的并发API来完结作业并不简略,比方运用Thread,ThreadPool,lock,Monitor等等,你无法太好的发展。不过.NET 4.0供给了一些美好的事物,咱们称之为.NET并行扩展。它是一种现代的并发模型,将逻辑上的使命并发与咱们实际运用的的物理模型别离开来。曾经咱们的API都是直接处理线程,也就是(上图)下方橙色的部分,不过有了.NET并行扩展之后,你能够运用更为逻辑化的编程风格。使命并行库(Task Parallel Library),并行LINQ(Parallel LINQ)以及和谐数据结构(Coordination Data Structures)让你能够直接重视逻辑上的使命,而不用关怀它们是怎么运转的,或是运用了多少个线程和CPU等等。
下面我来简略演示一下它们的运用办法。我带来了一个PLINQ演示,这儿是一些代码,读取XML文件的内容。这有个50M巨细的popname.xml文件,保存了美国社会安全数据库里的信息,包括某个洲在某一年的人口核算信息。这个程序会读取这个XML文件,把它转化成一系列目标,并存放在一个List中。然后对其履行一个LINQ句子,查找一切在华盛顿名叫Robert的人,再依据年份进行排序:
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;
咱们来履行一下……首要加载XML文件,然后进行查询。运用PLINQ咱们能够做到并行地查询。咱们只需复制一份代码……改成queryParallel……现在我仅有要做的只是在数据源上运用AsParallel扩展办法,这样便会引进一套新的类型和完结,此刻相同的LINQ操作运用的就是并行的完结:
var queryParallel =
from n in popNames.AsParallel()
where n.Name == targetName && n.State == targetState
orderby n.Year
select n;
咱们从头履行两个查询。
再次加载XML数据……并行完结运用了1.5秒,咱们再试着运转一次,一般成果会更好一些,现在或许刚好在履行一些后台使命。一般咱们能够得到更快的成果……这次比较接近了。现在你能够观察到,咱们并不需求做太多作业,便能够在我的双核机器上得到并发的作用。
这儿我无法确保说,咱们只需随时加上AsParallel便能够得到两倍的功能,有时能够有时不行,有些查询能够被并行,有的则不能够。可是,我想你必定赞同一点,运用如LINQ这样的DSL能够便利咱们编写并行的代码,也更有或许运用起并行作用。尽管不是每次都有用,可是测验的本钱也很低。假如咱们运用一般的for循环来编写代码,在某个当地运用线程池等等,便很简略在这些API里失掉方向。而这儿咱们只需简略地测验一下,便能知道是否能够进步功能了。
这儿你现已看到我运用的LINQ查询,而现在也有许多作业是经过循环来完结的。你能够幻想首要的运算是从哪里来的,很天然会是在循环里操作数据。假如循环的每个迭代都是独立的,便有很大的时机能够运用并发操作──我知道这儿是“假如”,不过长时间来看则必定会呈现这样的状况。这时分便能够运用并行扩展,或许说是.NET并行扩展里的新API,把循环转化成并行的循环,只需简略的改动……简直只需用相同的循环体把for重构成Parallel.For就行了。假如你有foreach操作就能够运用Parallel.ForEach,或是一系列次序履行的句子也能够用上Parallel.Invoke。此刻使命并行库会接收并履行这些使命,依据你的CPU数量运用最优化的线程数量,你不需求重视更深的细节,只需求编写逻辑就能够了。
就像我说的那样,或许你会有独立的使命但也或许没有,所以许多时分咱们需求编程言语来重视这方面的作业。比方“阻隔性(Isolation)”。例如,编译器怎么发现这段代码是独立的,能够安全地并发履行,比如我创建了一个目标,在同享给其他人之前,我对它的改动是安全的。可是我一旦把它们同享出去了,那么它们便不安全了。所以假如咱们的类型体系能够盯梢到这样的同享,如Linear Types──这在学术界也有一些研讨。咱们也能够在函数的纯洁性(Purity)方面下功夫,如重视某个函数是否有副作用,有些时分编译器能够做这方面的查看,它能够制止某些操作,以此确保咱们写出纯函数。还有就是不行变性(Immutability),现在的C#或VB,咱们需求额定的作业才干写出不行变的代码──但本不应这样,咱们应该在言语层面上更好的支撑不行变性。这些都是在并发方面需求考虑的问题。
假如说有哪个言语特性超出这个领域,我想说这儿还有一个准则:你不应希望C#中呈现某个特别的并发模型,而应该是一种通用的,可用于各种不同的并发场景的特性,就像阻隔性、纯洁性及不行变性那样。言语具有这样的特性之后,就能够用于构建各种不同的API,各种并发办法都能够运用到中心的言语特性。
(未完待续)