Hello World
Spiga

编程语言的发展趋势及未来方向(6):并发

2010-05-30 22:53 by 老赵, 11074 visits

这是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,各种并发方式都可以利用到核心的语言特性。

(未完待续)

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

19 条回复

  1. 老菜
    60.215.45.*
    链接

    老菜 2010-05-31 08:23:20

    沙发
    哥们,再多写点吧。

  2. shuhen
    222.173.95.*
    链接

    shuhen 2010-06-01 11:23:55

    问个很菜的问题:在传统的编程里面,是否子进程要比线程更容易实现并发?这种并发是编译器关心的多一些还是系统任务调度关心的多一些。

  3. shuhen
    222.173.95.*
    链接

    shuhen 2010-06-01 12:26:31

    当然,编译器是有很多种的,我只是说比较典型的,或者就说C#的也好。

  4. Snake
    120.43.212.*
    链接

    Snake 2010-06-01 12:34:12

    Good,老赵继续.

  5. 老赵
    admin
    链接

    老赵 2010-06-01 12:42:20

    @shuhen

    我不知道你是指什么“更容易”,就我来说用线程作并发更“容易”吧,比如可以直接通信。有人喜欢多进程的并发方式,可能隔离性更好,更稳定,只是跨进程通信总要麻烦和低效一些。

    不过这些应该都和编译器无关吧。

  6. shuhen
    222.173.95.*
    链接

    shuhen 2010-06-01 13:43:58

    @老赵

    我是这样理解的,在一个多核的cpu上并行运算会面临很多困难,比如说怎么保证这些并行任务的先后顺序、保证他们执行的结果是最初设计想得到的等等。而进程隔离性更好,所以更适合并行的实现(就是我说的“更容易”)。

    老赵提到了通信方面的问题,我想对通信的要求是否也会增加并行的困难(假设用线程替代进程就是为了通信方便的情况)。设想一个5核的cpu上并行了100个线程,每个核心有19个等待的线程(除去正在运行的一个),其中有个正在运行的线程A要和线程B通信,而线程B在某个核心等待队列靠后的位置,那这里可能就会出现情况,比如线程A会被挂起,但如果这类情况很多的话——比如这100个线程之间都有着千丝万缕的联系,是否会严重影响并行的效果,或至少降低了并行所带来的优势。

  7. 老赵
    admin
    链接

    老赵 2010-06-01 13:55:56

    @shuhen

    估计你误解了我说的隔离性是指什么,我不觉得隔离性对于并行实现有太大帮助。你关注的是任务调度方面,这用线程反而更容易控制,比如你可以关注一下.NET 4.0的并行库,这么好用的类库如果是要用多进程的话就相当不好写了。

    你要效率高,线程不是越多越好的,比如你有4个cpu,那么4个线程是最理想的,系统不会有多余的调度开销。如果你在4核的机器上做并行计算用到了100个线程,那么基本就说明你写错了。.NET 4.0的并行库,就是帮助你协调任务依赖关系,任务怎么分配给少量又足够的线程去执行。

  8. shuhen
    222.173.95.*
    链接

    shuhen 2010-06-01 14:39:39

    @老赵

    您是否是说,并行的处理要在程序设计上面多下功夫,比如我说的100个线程,可以把关系紧密的线程合并起来,这样就不会存在那么多乱七八糟的等待问题。

    看来是我最初的想法有问题,因为我看到你博文中说道 :C#中光靠一个Parallel实现并行不是最终解决途径,应该从语言层面去解决(希望我没有理解错,呵呵),因此我就想到是不是操作系统那边也要有所动作,但我只是简单的考虑到进程间相对独立,互不干扰,可以更“毫无顾忌”的并行,现在想来实在是有失偏彼了。

    非常感谢老赵耐心的解释,跟您讨论很高兴。

  9. 老赵
    admin
    链接

    老赵 2010-06-01 15:59:29

    @shuhen

    一个Parallel方法的确是不能解决所有问题的,在语言基础上提供支持是一个“方向”,一个设想吧,离实践还有点距离,至少C#是这样的,其他一些函数式编程语言,已经可以做到简单的自动并行(主要是计算方面,不是任务并行)。所以并行程序方面,至少目前,的确还是十分困难的……

  10. 链接

    zwt101927 2010-06-01 23:12:19

    一直以为老赵不写了,汗,原来搬这来了

  11. 链接

    pc 2010-06-02 20:35:29

    吃定老赵了, 还是自立门户好。

  12. TonyWoo
    58.251.171.*
    链接

    TonyWoo 2010-06-02 23:00:30

    翻译得不错,上个星期天看了一下视频,浑浑沉沉的最后还是睡着了,呵呵。

  13. 链接

    Ivony 2010-06-03 14:58:29

    我们也可以在函数的纯洁性(Purity)方面下功夫,如关注某个函数是否有副作用,有些时候编译器可以做这方面的检查,它可以禁止某些操作,以此保证我们写出纯函数。

    看来Anders的想法也是这样,C#引入public lambda int Function( int p )这样的语法有盼头了。

  14. michael
    60.171.124.*
    链接

    michael 2010-06-21 11:08:25

    这个系列我一直在跟着看。我觉得老赵的这个标题怪怪的,并发总是让我想到操作系统的时间片轮转啊,叫并行可能更好一些。毕竟是多核编程嘛

  15. 老赵
    admin
    链接

    老赵 2010-06-21 11:37:19

    @michael

    Concurrency是并发,Parallel是并行。这里不光是指多核的CPU密集型的并行计算,单核也要,也可以并发的……

  16. 螺杆泵
    180.168.194.*
    链接

    螺杆泵 2020-02-29 11:14:43

    巴基斯坦火车相撞一线医护战疫日记3.6万治愈患者出院美股大跌格林遭驱逐孙杨被禁赛8年孙杨回应被禁赛高考倒计时100天唐山市2.1级地震嘀嗒公司被约谈黎智英家中被捕日内瓦车展取消韩美联合军演延期陕西贫困县全摘帽保安拦截姚明

  17. jhrtws
    218.6.78.*
    链接

    jhrtws 2020-08-19 21:23:28

    近日,市场调研机构Counterpoint Research公布了8个国家第二季度前五名智能手机的销售榜单。报告显示,中国市场第二季度智能手机销量前五的手机分别为:iPhone 11、OPPO A8、荣耀9X、VIVO Y3和华为Mate 30 5G。其中,只有iPhone 11和华为Mate 30 5G能够算得上是旗舰手机,而它们的排名分别是第一和第五。豫韵休闲会馆

  18. zoroseo2020
    124.156.224.*
    链接

    zoroseo2020 2020-10-16 00:16:18

    巴基斯坦火车相撞一线医护战疫日记3.6万治愈患者出院美股大跌格林遭驱逐孙杨被禁赛8年孙杨回应被禁赛高考倒计时100天唐山市2.1级地震嘀嗒公司被约谈黎智英家中被捕日内瓦车展取消韩美联合军演延期陕西贫困县全摘帽保安拦截姚明 彩票福彩双色球

  19. cathalia
    34.92.184.*
    链接

    cathalia 2020-12-03 23:54:43

    隔音性能好。EPS产品隔音主要通过两个途径彩票,一是吸收声波能量福彩双色球,减少反射和传递幸运飞艇;二是消除共振,减少噪音。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我