没有合适的资源?快使用搜索试试~ 我知道了~
《理论计算机科学电子笔记》第41卷第1期(2001年)网址:http://www.elsevier.nl/locate/entcs/volume41.html16页在Haskell中开发高性能服务器应用程序,案例研究:HaskellWeb服务器西蒙·马洛微软研究院英国剑桥摘要服务器应用程序,特别是基于网络的服务器应用程序,对编程语言提出了独特的要求:轻量级并发和高I/O吞吐量都很重要。本文描述了一个用Concurrent Haskell编写的原型Web服务器,并给出了两个有用的结果:首先,一个符合要求的服务器可以用最小的排序来编写,从而在不到1500行的代码中实现,其次,朴素的实现产生了合理的性能。此外,对一些时间关键的组件进行微小的修改,将性能提高到除了负载最重的Web服务器之外的任何东西都可以接受1介绍互联网已经产生了它自己的应用领域:多线程服务器应用程序,能够与数百或数千个客户端同时进行交互例子包括FTP(文件传输协议),电子邮件传输,DNS(名称服务器),Usenet新闻,聊天服务器,分布式文件共享,以及最流行的:HTTP服务器。HTTP(超文本传输协议[Fie99])是用于在Internet上传输网页的协议。HTTP协议本质上是基于事务的。客户端首先打开到服务器的连接并发送请求消息。服务器解释请求,如果请求引用服务器上的有效文档,则向客户端发送文档的内容。在HTTP协议的早期版本中,客户端和服务器之间的连接将在此时关闭,要求客户端为每个文档打开一个新连接1电子邮件地址:simonmar@microsoft.com2000年1月,出版社dbyElsevierScienceB。 V.操作访问和C CBY-NC-ND许可证。请求过程中HTTP协议的最新修订版允许连接保持开放,以便进行进一步的传输(称为keep-alive连接)。HTTP协议的本质对服务器实现提出了某些要求:• 并发性是必不可少的,因为服务器不允许自己被捆绑由一个慢的客户端,或者一个客户端从服务器请求一个大的文档。 并发模型的选择对服务器的性能影响最大,我们将在第2节中讨论。服务器应该能够以处理大量客户同时传送文档而不影响性能。• 对于小请求,服务器必须能够快速处理请求以避免占用资源太久。这意味着新连接的延迟应该尽可能低。服务器性能的这一方面可以通过以不断增加的速率向其发出小请求来衡量;在某个时候,服务器的性能将开始下降,因为速率增加超过等待时间,并且正在进行的连接的积压积累。低延迟的另一个优点是服务器更能抵抗拒绝服务类型的攻击。如果它可以尽快抛出虚假请求,那么当服务器被恶意客户端的请求阻塞时,整体服务器性能受到的• 容错和性能一样重要:服务器应该能够从错误中优雅地恢复,并且永远不会崩溃(或者如果它崩溃了,安排它自己重新启动,以便服务中断最小)。 它也应该可以重新配置服务器,而不需要关闭它为什么要用Haskell编写Web服务器首先,因为我们可以!如果我们能在重要的应用领域(如Web服务)进行有效的竞争,对Haskell来说并其次,Haskell有很多相关的属性可以带给派对:• Concurrent Haskell [PJGF96]提供了一个轻量级的并发模型,有助于为多个并发客户端提供低延迟和低开销,这对于良好的服务器性能至关重要。• 最近支持异常的扩展[PJRH+99]为处理运行时错误提供了有用的工具• 异步同步[MJMR01]允许简洁的实现重要的功能,如超时,我们将在第4节中看到。异步异常对于允许服务器响应外部刺激也很有用• 我们在构建Web服务器时使用了大量的库,包括网络库、解析组合器库、HTML生成库和POSIX系统接口库。这些图书馆都不是作为Haskell标准的一部分,但所有这些都与GHC编译器一起分发。本文描述了我们的Web服务器实现,重点是需要扩展Haskell的服务器实现表现良好,我们将在第7节中展示。它也是可靠的-我们测试它作为替代服务器的haskell.org网站,在那里运行了一个星期,在这段时间内收集了大约2000次点击,并保持在3 M的内存占用。该测试发现了HTTP协议实现中的一个错误,该错误已被修复。我们希望在将来的某个时候用Haskell实现取代主haskell.orgWeb服务器(目前是Apache)2并发模型并发模型的选择是Web服务器设计的关键在本节中,我们将简要介绍常用的模型,列出它们的优缺点,并将它们与Concurrent Haskell的方法进行比较2.1单独的进程这是Apache使用的模型,其中每个新连接都由机器上的新程序。一个顶级进程监控传入的连接,并产生一个新的工作进程与每个客户端对话优点:• 易于实施。• 自动利用多个处理器。缺点:• 就 启 动 和 关 闭 成 本 、 上 下 文 切 换 开 销 和 每 个 进 程 的 内 存 开 销(Apache维护一个备用进程的缓存,以减少新连接的启动时间)。• 进程间通信很困难。进程间通信是Web服务器操作的某些方面所必需的。例如,通常只有一个日志文件记录trans-action事件(参见第5节),因此多个线程必须合作才能访问日志文件。2.2操作系统线程操作系统线程可以映射到进程上,在这种情况下,开销将与单独的进程相似(现代操作系统将在任何情况下在分叉进程之间共享页表),或者它们可以实现为轻量级内核线程,在这种情况下,开销将更低。优点:• 进程间通信更容易。• 自动利用多个处理器。缺点:• 相当重的重量,虽然可能比单独的过程轻• 编写多线程代码比单线程代码更难,也更容易出错。2.3具有I/O多路复用的单片工艺另一种方法是直接实现所需的并发性,放弃操作系统本身提供的任何分时功能这种方法的唯一要求是能够多路复用多个I/O通道。用于在单个进程中多路复用I/O的现有方法包括:• 使用POSIX这些函数同时测试多个文件描述符,返回哪些描述符可用于读取或写入的信息 这个想法是然后应用程序执行任何可用的读和写(使用非阻塞I/O),然后返回到打开文件描述符列表上再次调用select()这种方法的前提是select()是O(n)的问题,其中n是被测试的文件描述符的数量,因为应用程序必须建立一个长度为n的列表来传递给select(),操作系统必须遍历这个列表来建立结果。非阻塞I/O的另一个问题是,它• 异步I/O、POSIX实时信号和内核事件队列。这些都是缓解上述select()问题的方法。它们是相对较新的非标准功能,并非所有操作系统都支持。但是,任何使用select()的实现都可以转换为使用这些替代方法之一,而相对较少的开销。这些方法的优点:• 非常快,尤其是后一种方法。缺点:• Web服务器本质上是一个多线程的应用程序,因此没有并发抽象的编程注定是痛苦的。有几个Web服务器使用这些方法,它们是目前最快的服务器。2.4用户空间线程用户空间线程本质上是单个进程中线程抽象的实现(或者可能在少量操作系统线程之上;见下文)。程序员可以使用该语言提供的并发原语来编写他/她的应用程序,用户空间线程实现将提供低级的分时和I/O多路复用支持。用户空间POSIX线程的几种实现存在于Unix中。并发Haskell(或者至少是GHC中的实现)也是这种模型的一个实例;Haskell运行时系统在单个操作系统进程中运行,并多路复用许多Haskell线程。为了支持多个Haskell线程同时执行I/O,运行时系统可以在上一节中描述的I/O多路复用选项无论这是如何实现的,程序员都看不到选择。在某种程度上,用户空间线程提供了两个世界中最好的。并发器是轻量级的,程序员不需要关心I/O多路但是有一个缺点:用户空间线程包通常不能利用主机上的多个处理器。GHC开发团队目前正在致力于Concurrent Haskell的实现,通过使用少量的操作系统线程来分担负载,从而避免这种缺陷3Web服务器的结构3.1主循环主循环非常简单:acceptConnections:: Config -> Socket -> IO() acceptConnections conf sock = do(handle,remote)-接受sockforkIO(catch(talkconf handle remote)acceptConnections conf sockacceptConnections接受Config类型的服务器配置和侦听套接字,并等待套接字上的新连接请求。当接收到连接请求时,一个新的工作线程被forkIO派生,主循环返回到等待连接。工作线程调用talk(talk的定义在下一节中给出),这是HTTP中与客户端通信的主要函数这里有趣的部分是,如果在对话期间引发异常会发生什么。finallycombinator允许指定严格的排序,独立于异常:最后:IOa ->IO b->IO a这个combinator的行为很像Java中的finally。它执行第一个参数,然后执行第二个参数(即使第一个参数引发了异常),然后返回第一个参数的值(或重新引发异常)。在上面的主循环中,我们如果我们遇到任何类型的错误,包括我们代码中的bug,客户端将被正确关闭。尽管Haskell运行时系统会自动关闭被确定为未使用的文件,但尽早关闭它们以释放与文件相关的资源是有益的通话也包含在catchcombinator2中:catch:: IO a->(Exception-> IO a)-> IO a这个组合子执行它的第一个参数,如果引发异常,则将其传递给第二个参数(异常处理程序),否则返回结果。与finally相反,catch指定仅在引发异常时执行的操作,而finally指定的操作总是要被执行的。acceptConnections的代码使用catch捕获任何错误并将其记录到错误日志文件中,我们将在第5节中描述。3.2HTTP协议实现服务请求是一个简单的管道:(i) 从套接字读取请求,(ii) 解析请求,(iii) 生成响应,(iv) 将响应发送回客户端,(v) 如果要保持连接有效,则返回步骤1。从套接字读取请求是通过getRequest:getRequest::Handle -> IO[String]执行的它接受代表与客户端通信的套接字的文件句柄,并返回一个字符串列表,每个字符串都是请求的一行。一个典型的请求看起来像这样:GET/index.htmlHTTP/1.1Host:www.haskell.org2这个catch不同于Haskell中的标准IO.catch;这里我们指的是来自GHC异常库的异常.catch日期:2000年5月31日星期三格林尼治标准时间11:08:40第一行给出了命令(本例中为GET)、请求对象的名称以及客户端使用的HTTP协议的版本。随后的行,称为标题,提供额外的信息,并且大多是要求服务器忽略任何它不理解的头下一个阶段是将请求解析为请求:data Request =Request {reqCmd::RequestCmd,reqURI:: ReqURI,reqHTTPVer::HTTPVersion,reqHeaders::[RequestHeader]}请求记录包含命令名(在上面的示例中为GET)、请求的URI3、HTTP协议版本客户端所使用的,以及可选头的列表。服务器需要根据客户端使用的协议版本来解释请求,尽管它可以使用其本地协议版本进行响应;该协议被设计为向上兼容。请求由parseRequest解析:parseRequest:: Config -> [String] -> Either Response Request请注意,解析请求可能会返回一个响应:这表示失败,并且在大多数情况下,响应将是接下来,我们生成响应:data响应=响应{ respCode::Int,respHeaders::[ResponseHeader]、respCoding::[TransferCoding]、respBody::ResponseBody、respSendBody::Bool}数据响应体=无正文| FileBody文件路径{-size-}| HereItIsHTMLgenResponse:: Config -> Request -> IO ResponsegenResponse对请求的有效性执行大量检查,并生成适当的响应。一个有效的GET请求将导致一个重3统一资源指示符,一种更通用的URL形式一个无效的请求将导致一些描述的错误响应,一些自动生成的HTML在主体中描述错误(HereItIs主体类型就是为了这个目的)。在响应主体由整个文件逐字组成的常见情况下,Response结构的respBody组件这样我们就可以使用更有效的方法将文件发送到客户端,而不是简单地将内容转换为String。最后一步是向客户端发送响应sendResponse:: Config-> Handle-> Response-> IO()把所有这些放在一起,顶层的talk函数看起来像这样:talk:: Config -> Handle -> HostAddress -> IO() talk conf handle haddr= dostrs- getRequest handle caseparseRequeststrs of左resp -> sendResponse conf handle resp右req -> doresp- genResponse conf reqsendResponse conf handle resplogAccess请求响应haddrif(isKeepAlive请求)then talk conf handle haddrelse return()实际上,还有一些额外的代码来处理捕获和记录错误(第5.1节)和超时(第4节)。对logAccess的调用导致一个条目被写入描述事务的日志文件,参见第5节。4超时Web服务器需要某种形式的超时机制,以便挂起或响应时间过长的客户端可以断开连接,释放与连接相关联的资源。基本上,我们需要的是一个通用的超时组合子,其类型如下:timeout:: Int--以秒为单位-> IO a-- action to run-> IO a-- action to run on timeout-> IO a应用程序超时ta b的行为应该如下:a一直运行,直到它完成或经过t秒。如果它在时间t内完成,则timeout立即返回结果,否则a将终止并出现异常b被执行。如果a(或b,在超时的情况下)引发异常,则该异常将通过超时传播。timeout函数没有其他副作用,因此timeout可以任意嵌套。多亏了异步异常[MJMR01],我们可以用上面的属性实现一个超时组合子。 注意,因为动作a可以在任何时候被异步异常终止,所以它应该是异常安全的,也就是说,它必须确保不会让任何可变数据结构处于不一致的状态或泄漏任何资源。事实上,我们所有的代码都应该被写成异常安全的,因为像堆栈覆盖堆栈和堆覆盖堆栈这样的异常是异步传递的。有两个原语在编写异常安全代码时很有用block:: IO a -> IOa unblock:: IO a->IO a其中块a执行a,异步异常被阻塞,也就是说,任何希望在当前线程中引发异步异常的线程必须等待,直到异常再次被解除阻塞。类似地,在执行期间取消阻止异步异常。 block和unblock的应用程序可以任意嵌套。下面块(doa<- takeMVar m(unblock(...))'catch'(\e-> do putMVarma;throwe)putMVar ma)使用组合子,如finally(在上一节中描述)和括号:bracket:: IO a->(a-> IO b)->(a-> IO c)-> IO c也有助于编写异常安全代码。例如,编写上述锁定序列的一种更简单的方法是bracket(takeMVar m)(putMVar m)(.)关于Haskell中异步异常的完整故事,包括上面超时组合子的实现,可以在[MJMR01]中找到5测井Web服务器通常会生成日志文件,列出所有请求以及有关服务器发送的响应的某些信息中的每个条目日志通常记录• 在接收到请求时,• 请求者• 请求的URL• 响应代码(成功或某种失败),• 传输的字节数• 完成请求所花费的时间• 客户端软件• 引用URL。日志条目的格式是可配置的,并且可以包括来自请求或响应的其他字段。 存在标准日志条目格式,流行的服务器和处理日志文件以生成报告的软件。出于这个原因,我们决定Haskell Web服务器应该能够生成兼容的日志。工作线程通过调用logAccess:logAccess::Request-> Response -> HostAddress -> TimeDiff -> IO()传递请求、响应、客户端地址以及接收请求和完成响应之间日志条目的实际生成和日志条目到文件的写入由单独的线程4完成。工作线程通过调用logAccess,通过全局无界通道与日志子系统通信。日志记录线程从通道中删除项目并生成日志条目,然后将其写入日志文件。将日志记录放在一个单独的线程中是一个好主意,原因如下• 它有助于减少系统的总负载,因为工作线程可以在日志条目被写入之前完成,因此被垃圾收集。• 这意味着服务多个请求的线程可以立即处理下一个请求,而无需等待第一个请求的日志条目被写入。• 日志线程可以批处理多个请求,并一次性将它们写出来,这可能比一次写一个更有效。日志记录线程被设计为容错的:如果它收到任何类型的异常,它会尝试通过重新打开日志文件进行写入来重新启动自己,并继续下一个日志请求。 这种行为被主循环使用,每当它接收到重新读取其配置文件的请求时,它需要重新启动日志记录线程。4 或者线程,如果资源丰富!5.1错误日志记录记录服务器错误的方式与记录请求的方式类似。一个单独的线程将日志条目写入错误日志文件,从全局通道获取日志请求。异常处理程序分散在主请求/响应处理代码周围,这些代码捕获异常并记录它们,并在将异常传递到顶层处理之前,用如果错误日志线程接收到异常,它也会重新启动自己(并将此事件记录到日志文件中)。5.2全局变量在日志记录线程的上述描述中,我们提到了工作线程和日志记录线程之间的通信是经由“全局通道”的如何在Haskell中定义全局通道?这是全局可变变量的一个实例,这个概念对于那些用命令语言编程的人来说很熟悉,但直到最近Haskell程序员才使用。例如,全局MVar可以声明为global_mvar::MVar Stringglobal_mvar = unsafePerformIO newEmptyMVar尽管我们已经使用unsafePerformIO声明了全局mvar,但这种声明通常是完全安全的(但是,请参阅后面的警告)。我们仍然使用标准的putMVar和takeMVar操作访问MVar。我们可以认为newEmptyMVar是在程序初始化时执行的,但事实上,什么时候执行动作并不重要,只要它发生在全局mvar首次被访问之前。事实上,这一行动很可能是懒惰地发生的,被推迟到第一次要求全局mvar。当需要可变变量的单个实例时,全局可变对象特别有用,因为它们避免了显式传递对象的需要。但是,有几点需要记住:• 严格地说,这种使用unsafePerformIO是不安全的,因为如果全局mvar的出现被替换为它们的值,即(unsafePerformIO newEmptyMVar),程序现在的行为就不同了。为了阻止这种情况发生,我们必须规避编译器中的任何优化,这些优化可能会用其值替换全局var。在GHC中,这相当于添加了{-# NoInline global\_mvar #-}在源代码的某个地方。• 必须注意为全局变量提供类型签名,而不是声明具有多态类型内容的全局可变变量。如果违反了这一规则,类型安全就处于危险之中,因为⊥多态类型的变量可以被提取并用于任何类型(这个问题在[LLC99]中有更详细的描述• 在并发程序中,当多个线程可以访问变量时,使用MVar而不仅仅是IORef是很重要的(IORef是一个普通的可变变量,而MVar增加了同步)。然而,如果我们遵守这些规则,全局可变变量是一个有用的概念。我们在Web服务器中的以下地方使用全局可变变量:• 存储辅助线程与日志记录线程通信的通道。• 存储日志记录线程的ThreadId,以便主线程可以向它们发送异常以重新启动它们。• 存储命令行选项。 程序只启动一次,所以 这是一个只写一次的可变变量。只写一次的可变变量可以半安全地从纯的、非IO的代码中读取:如果可变变量的初始值是,那么如果程序试图在变量的值被写入之前访问该变量,使用全局变量的一个更简洁但效率较低的替代方法是使用隐式参数[LSML00]。我们还没有调查过这条路线。6运行时配置我们的网络服务器是通过编辑文本文件进行加密的,与其他流行的网络服务器类似。配置文件的语法与Apache的语法类似。当服务器启动时,它解析配置文件,如果没有发现错误,则立即开始服务请求。为了高可用性,Web服务器最好也是运行时可配置的。例如,当新内容被放置在服务器上时,管理员需要以某种方式通知服务器新内容可用以及在哪里找到它。有时需要在运行的服务器上更改关闭服务器并使用新的配置重新启动它将是不令人满意的,因为在重新启动期间,站点将处于脱机状态。因此,运行中的服务器应该能够重新读取其配置文件,而不会中断操作。但是已经在进行中的交易怎么办?他们应该立即看到新的配置吗?在我们的服务器中,我们采取的方法是,新的配置应该只对新的连接有效,而现有的连接应该被允许继续使用旧的设置。此方法避免了在请求进行时更改配置设置的许多问题:例如,如果更改了安全设置,不再对请求它的客户端可用,是否应终止传输? 事实上,这种行为是可取的,但一个大锤解决方案是在需要更改任何安全设置时完全重新启动服务器在Haskell Web服务器中实现运行时配置更新是很简单的:正如我们配置在创建工作线程时传递给它,所以当配置改变时,我们需要做的就是确保任何新线程都接收到新的配置。我们采取的方法是在主线程应该重新读取配置文件时向其发送一个异步异常。这为我们提供了几种强制更改配置的方法• 类Unix操作系统上的信号这是让进程重新读取其配置文件的传统方式这个方法在我们的Web服务器中实现如下:传入的信号导致一个新线程启动,它立即向主线程发送一个异常主线程捕获信号并重新读取配置文件。• 实现专有的HTTP命令,管理员可以使用该命令在线和远程重新配置服务器。如果使用这种方法,肯定需要安全授权• 主机操作系统提供的任何其他类型的进程间通信。7性能结果在本节中,我们将展示Haskell Web服务器的初步性能结果。7.1性能调整我们对服务器的初始实现做了一些调整,以消除一些较大的性能瓶颈。• 我们将使用getContents和hPutStr的简单文件传输代码替换为直接向数组执行I/O的版本的bytes。GHC• 默认情况下,GHC的调度程序上下文每秒切换约5000次。我们将其降低到更合理的值,50次/秒,结果有很大的不同。原因是GHC正如第2节所讨论的,select是O(n)的,所以当系统负载很重时,减少执行它的次数是一个胜利。750700650600550500450400350300250300 400 500 600 700 800 90请求速率(1/s)图1. 连接延迟结果• 调整垃圾收集设置有一些效果,特别是增加了分配区域的大小。默认情况下,GHC会根据程序的需求增加堆的使用量• 由于GHC的I/O库中hGetLine的重写此函数可将性能提高10%左右。• GHC终结器通常在线程中运行,但这对于Web服务器来说是昂贵的,因为大多数连接都会产生两个终结器:一个用于套接字本身的句柄,另一个用于正在传输的我们将finalization机制改为每次垃圾收集后在单个线程中进行批量finalizer,这导致了整体性能的小幅提升。请注意,这些调整中只有一个,即文件传输代码的优化,是对Web服务器代码本身进行的,其余的都是对GHC的运行时系统和库的调整实际上,Web服务器是洞察GHC并发性和I/O支持中的性能瓶颈的有用来源“Haskell Web服务器”回复率(1/s)7.2连接延迟这些测量是使用httperf[MJ]进行的,这是一种可以用于以特定速率生成请求的工具它主要用于确定Web服务器在性能开始下降之前可以维持的连接请求速率。在这个测试中,服务器机器是运行Linux 2.2的单处理器PII/450。客户端是本地100 Mbit以太网连接上的一台单独的机器。每次测试发送的请求总数为4000。所有的请求都是针对同一个1k文件的。客户端上的超时设置为1秒。响应率与每秒发出的请求的关系图见图1. 这清楚地显示了服务器是如何跟上客户端的,直到请求速率上升到服务器可以处理的速率之上而不累积积压(大约每秒710个请求),此时性能开始急剧下降。为什么性能会如此急剧下降?两个可能的因素是:• 当正在进行的连接在服务器上累积时,GHC调度程序使用的select()• 随着系统中线程数量的增加,垃圾收集的成本也会增加。垃圾收集在活线程数上必然是O(n),因为它必须遍历活动线程队列以确定哪些线程是活的。服务器当前不限制正在进行的连接的数量,实际上,在测试期间,在850个连接/秒的速率下,在服务器上观察到的并发连接的数量最高超过700个对并发连接的数量设置一个限制将有助于在drop-o连接点之后重新添加在相同的硬件上,Apache(最常用的Web服务器软件)最高达到950个请求/秒,并且吞吐量的下降不那么急剧。Apache默认将活动连接数限制为256,任何超过此限制的传入连接都会被操作系统拒绝。为了正确地看待这些数字,网络上负载最重的Web服务器(例如。http://www.yahoo.com/)平均每秒约5000次点击,峰值可能为每秒10000次点击。这些站点使用具有负载平衡安排的相同配置的服务器的集合来在可用机器之间传播请求。然而,对于网络上的大多数网站来说,我们的Haskell Web服务器的性能8结论这里给出的主要结果是,我们在Haskell中构建了一个符合HTTP/1.1标准(以及更多)的Web服务器,使用了不到1500行的Haskell(不包括库代码),并且最终的服务器在现实世界的条件下表现出色。此外,它是容错的,并在一个持续的时间内运行在一个恒定的,少量的内存中为了实现这一点,我们不得不使用Haskell的一些扩展,主要的是并发和异常。我们还使用了大量的库代码,所有这些都是GHC的库集合的一部分我们使用的库包括一个网络库、一个解析组合子库、HTML生成库和POSIX接口库。引用[Fie99] R. 超文本传输协议- HTTP/1.1,1999年6月。RFC 2616。[LLC99]J Launchbury,JLewis和B Cook。在haskell中嵌入ACM SIGPLANInternational Conference on Functional Programming(ICFP'99),第60-69页,巴黎,1999年9月Press.[LSML00]J. Lewis,M.希尔兹,E. Meijer,andJ. Launchbury.隐式参数:静态类型的动态作用域。第27届ACM SIGPLAN-SIGACT Symposiumon Principles of Programming Languages(POPL[MJ]大卫·莫斯伯格和泰金。 httperf-一个测量web服务器性能的工具。http://www.hpl.hp.com/personal/DavidMosberger/httpperf.html。惠普研究实验室。[MJMR01] Simon Marlow,Simon Peyton Jones,Andrew Moran和John Reppy。haskell中的异步异常。在ACM SIGPLAN编程语言设计和实现会议上,Snowbird,犹他州,2001年6月20日S. L. Peyton Jones,A.D. Gordon,and S.芬恩并行Haskell。在proc POPL'96,第295-308页,St. 佛罗里达州彼得斯堡,1996年1月Press.[PJRH+99] S. L. Peyton Jones,A. Reid,T. Hoare,S. Marlow和F.亨德森不精确异常的语义 在proc PLDI'99,ACM SIGPLAN通告的第34(5)卷,第25-36页。ACM Press,May 1999.
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 4
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 收起
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
会员权益专享
最新资源
- zigbee-cluster-library-specification
- JSBSim Reference Manual
- c++校园超市商品信息管理系统课程设计说明书(含源代码) (2).pdf
- 建筑供配电系统相关课件.pptx
- 企业管理规章制度及管理模式.doc
- vb打开摄像头.doc
- 云计算-可信计算中认证协议改进方案.pdf
- [详细完整版]单片机编程4.ppt
- c语言常用算法.pdf
- c++经典程序代码大全.pdf
- 单片机数字时钟资料.doc
- 11项目管理前沿1.0.pptx
- 基于ssm的“魅力”繁峙宣传网站的设计与实现论文.doc
- 智慧交通综合解决方案.pptx
- 建筑防潮设计-PowerPointPresentati.pptx
- SPC统计过程控制程序.pptx
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功