11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

PDC 09:并行和异步编程中的挑战及 F#的应对方案

  • 2009-11-22
  • 本文字数:2639 字

    阅读完需:约 9 分钟

在最近举办的 PDC 09 大会中,F#的程序经理 Luke Hoban 发表了一场名为“ F#并行与异步编程”的演讲,其中提出了并行与异步编程的多个重点与挑战,并解释了 F#是如何从语言特性及类库框架两方面来给出合适应对方案。

F#是.NET 平台上的又一门编程语言,结合了函数式编程及面向对象编程两种编程范式。微软研究院在 5 至 6 年开始着手设计并开发 F#,并且将随 Visual Studio 2010 一起发布其稳定版本及相关工具包,可用于产品的开发。与 C#和 VB 一样,F#是一门强类型的静态语言。Luke 指出,F#是一门通用语言,可用于各种程序的开发,不过它的许多特性非常适用于开发那些算法性强,或是并行和异步占较大比重的应用程序。

Luke 在演讲中提出了并行编程的四项挑战,其中第 1 个是“状态共享”。这里的共享状态特指可变(mutable)的状态,即可能被多个并行的组件所同时修改的内存。当遇到这样的情况,这意味着每次修改都可能影响多个组件,如果处理不当便可能造成难以预料的情况。而此时,从逻辑上分割各组件事实上也并非互相独立的,这给系统维护带来了困难。这种情况也很难测试,因为重现某个测试需要各并行的组件都处在特定的状况下。最大的问题可能是这样的代码很难高度并行,多个并行的组件如果需要共享同一块内存,则几乎一定会用到锁。锁很难处理,因为这非常依赖于各个组件是如何使用,以及何时使用某块内存的。如果程序新增了一个组件,甚至只是对现有组件做出少量修改,这可能就会让并行的应用程序对新的内存造成共享,于是便不知不觉地破坏了线程安全。

F#提出在语言特性上强化不可变的(immutable)程序开发方式。不可变的编程方式表示尽可能避免那些可修改的内存数据。在 F#中,默认情况下的所有变量、函数、参数等等,一旦绑定(bind)至某个标识符后都是不可修改的。F#中的一些常用的数据类型,如 Record,Tuple 或 Discriminated Union 都是不可变的。如果想要一个状态不同的对象,开发人员只能“新建”而不能“修改”,而 F#也提供了一定的语言特性来辅助此类操作。由于不可变性,在 F#中便可以轻松使用各种方式进行并行计算,而不必担心线程安全问题。例如,可以使用.NET 4.0 中的 PLinq——在 F#则被封装为 PSeq 模块进行序列的映射,过滤或求和等操作。此外,F#还提供了如 List,Set,Map 等不可变的常用数据结构。对于它的面向对象编程的部分,Luke 指出 F#也拥有一些特性,鼓励开发人员构建不可变的类型,即没有 set 操作,每个方法都只是根据参数进行计算并返回结果,而不是改变内部状态的类型。

Luke 提出的第 2 个挑战是异步编程中的控制切换(Inversion of Control)问题。他认为,开发人员一直习惯于编写顺序的程序,即使用一行代码接着另一行代码的方式来实现逻辑。但是对于一些耗时很长的操作来说,这么做会阻塞程序的主线程,如在 UI 程序中阻塞主线程则会引起界面的僵死,此时往往需要异步调用。但是,异步调用需要将程序逻辑分为两个或多个阶段,在执行完一个阶段之后,再将结果通过回调函数传递给下一个。但编写这样的代码非常困难,往往需要为异步程序的控制编写大量代码,例如异常处理或任务取消等等。传统.NET 异步编程模型,如解耦的 Begin/End 方法都无法解决这个问题。当需要异步调用的逻辑越来越多,甚至需要在其中加入一些循环或判断等逻辑,那程序的编写很容易变得越来越复杂。

F#中提供了一个名叫工作流(Workflow)的语言特性来应对这个问题。Workflow 可以被认为是 F#版本的 monad 实现,它的主要特色便是由编译器对顺序编写的代码进行 desugar 操作,形成回调的方式便于异步执行其中某些步骤。Luke 演示了一个使用 C#编写的,从 Azure 云中下载图片的 WPF 应用程序,其中长时间同步操作导致界面僵死。而将这段同步逻辑转化为异步则需要好几页的代码,其中的主要问题便是原本简单的 for 操作必须交由额外的上下文对象来保存,这样逻辑便在业务部分及异步控制部分中不断切换,造成难以实现和维护的代码。而使用 F#实现相同的工作时,只需要使用 async {…}将原有的逻辑包装起来,便形成了一个异步工作流。然后再将其中的一些耗时操作的 let 和 do 指令修改为 let! 或 do!,这样便告知 F#这两个步骤在执行时需要将控制权交还给框架,在得到结果之后才通过回调函数继续执行后面的逻辑。代码中原本的 for 循环可以被 F#正确的处理,其表现形式和顺序的代码逻辑可谓毫无二致。值得一提的是,演示中 Luke 使用 F#构建的类库可以直接被 C#编写的 WPF 应用程序使用,唯一的修改只是引入了不同的命名空间而已。

第 3 个挑战是应用程序与 I/O 设备的交互,例如磁盘或是远程的云,这便是 I/O 密集型(I/O Bound)逻辑。由于各种 I/O 设备(如硬盘及网卡)往往是独立的,因此需要同时发起多个 I/O 请求才能够充分利用资源,提高程序的性能及响应能力。这便涉及到 I/O 并行(I/O Parallelism)。而使用 async { … }所形成的多个异步工作模块可以由 F#组合成单个异步工作块,然后作为.NET 4.0 中的任务(Task)执行。每个异步工作块中的 I/O 异步操作使用 let! 指令,在工作时可以将控制权交由 F#,而保持原有逻辑的顺序性。由于每个 I/O 操作都是异步的,它并不会占用应用程序的工作线程。因此,即便是同时发起许多 I/O 请求,从任务管理器中也可以发现应用程序其实只使用了少量的线程。

最后一个挑战,是指并行应用程序往往只能简单实现向上扩展(Scale Up),而难以扩展至许多廉价机器所组成的集群。如果要有良好的向外扩展(Scale Out)能力,必须从程序设计初期便抱有这样的想法。这往往意味着使用消息和代理(agent)进行编程,它是一种为并行程序提供扩展能力的基础方式。 Erlang 及微软的 Axum 都使用了类似的思想,F#也提供了 Agent 组件,在每次发布过程中这个组件也在不断演化。使用基于 Agent 的方式,各组件的依赖便消失了,它们完全通过消息传递进行通信。F#的 Agent 组件是 MailboxProcessor,它的 Start 函数会提供一个 inbox。开发人员可以使用 inbox 的 Receive 方法发起一个非阻塞的接受操作,由于使用了异步工作块及 let! 指令,这行代码并不会阻塞线程,而是把控制权交由 F#,直至获得一个消息。每个 Agent 对象都是非常轻量的对象,它与线程并没有对应关系。因此,即便是创建了大量的 Agent 对象也不会占用太多系统资源,F#会基于.NET 4.0 中的 TPL 来合理并充分利用计算能力。

你可以在 PDC 2009 的网站上浏览或下载本次演讲的完整录像及幻灯片等资源。你也可以访问 InfoQ 中的 F#栏目来获得更多相关内容。

2009-11-22 02:354167
用户头像

发布了 157 篇内容, 共 48.5 次阅读, 收获喜欢 4 次。

关注

评论

发布
暂无评论
发现更多内容

Backbone 之 Inception:纵横交错 (Pytorch实现及代码解析

爱好编程进阶

Java 程序员 后端开发

Day182

爱好编程进阶

Java 程序员 后端开发

ElasticSearch 概述

爱好编程进阶

Java 程序员 后端开发

docker安装与启动

爱好编程进阶

Java 程序员 后端开发

flume基本概念与操作实例(常用source)

爱好编程进阶

Java 程序员 后端开发

30个类手写Spring核心原理之Ioc顶层架构设计(2)

爱好编程进阶

Java 程序员 后端开发

SAP 电商云的 Spartacus Storefront 如何配置多个 JavaScript Application

Jerry Wang

angular SPA SAP 5月月更 电商云

CGB2107-DAY07总结复习

爱好编程进阶

Java 程序员 后端开发

Day220、nginx快速入门 -nginx

爱好编程进阶

程序员 后端开发

关于Flutter中的RichText组件,你了解多少?

坚果

5月月更

2021年学习Java还有意义吗?

爱好编程进阶

Java 程序员 后端开发

AQS源码解读(番外篇)

爱好编程进阶

Java 程序员 后端开发

B站疯传20W份整套2021大厂面试1000题最新汇总(附视频答案详解)

爱好编程进阶

Java 程序员 后端开发

7月编程语言排行榜来了,为什么不同媒体报道的结果不一样?

爱好编程进阶

Java 程序员 后端开发

@requestMapping参数详解

爱好编程进阶

Java 程序员 后端开发

Day159

爱好编程进阶

程序员 后端开发

Dubbo源码分析- 总体介绍与模块划分

爱好编程进阶

程序员 后端开发

PDC 09:并行和异步编程中的挑战及F#的应对方案_.NET_赵劼_InfoQ精选文章