在上一个部分咱们对比了Twisted与 Erlang,并将注意力集中在它们共有的一些思想上.结果代表使用Erlang也是很是简便的,由于异步I/O和反应式编程是Erlang运行时和进程模型的关键元素.python
今天咱们想走得更远一点,去看一看 Haskell —— 另外一种功能性语言,然而与Erlang有很大不一样(固然与Python也不一样).这里面没有太多的平行概念,但咱们仍然会发现藏在下面的异步I/O概念.react
F —— 功能性
git
虽然Erlang是功能性语言,它主要关注可靠的并发模型.Haskell,另外一方面,是彻头彻尾功能性的,它无耻地利用了范畴论的概念,如函子 和 单子.程序员
不要慌.咱们这里不会涉及那些复杂的东西(虽然咱们能够).相反,咱们将关注一个Haskell的更加传统的功能性特性:惰性. 像许多功能性语言同样(除了Erlang), Haskell支持 惰性计算. 在懒惰计算语言中,程序的文字并不过多的描述怎样计算须要计算的东西.具体实施计算的细节通常留给了编译器和运行时系统.github
同时,须要进一步指出,做为惰性计算推动的运行时可能一次只计算表达式的一部分(惰性的)而不是所有.通常地,运行时只提供维持当前计算继续所需的最小计算量.编程
这里有一个使用Haskell head
语句的简单例子,这是一个提取列表第一个元素的函数,对于列表[1,2,3](Haskell与Python分享一些列表句法):服务器
head [1,2,3]
若是你安装了GHC Haskell运行时,你能够本身试一试:网络
[~] ghci GHCi, version 6.12.1: http://www.haskell.org/ghc/ : ? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. Prelude> head [1,2,3] 1 Prelude>
结果是 1, 正如所料.数据结构
Haskell列表的句法包含从前几个元素定义列表的使用功能.例如,列表[2,4,..]是从2开始的偶数序列.到哪结束呢?实际并不结束.Haskell列表[2,4,..]和其余如此表述的都是(概念上)无限列表.你能够在交互式Haskell提示符下计算它,这将试图打印这个表达式的结果以下:多线程
Prelude> [2,4 ..] [2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146, ...
你不得不按 Ctrl-C
终止计算由于它本身不会停下来.但因为是惰性计算,在Haskell中应用无限列表是没有问题的:
Prelude> head [2,4 ..] 2 Prelude> head (tail [2,4 ..]) 4 Prelude> head (tail (tail [2,4 ..])) 6
这里咱们分别获取无限列表的第1、2、三个元素,没看到任何无限循环.这就是惰性计算的本质.Haskell运行时只构造完成head
函数所需的列表,而不是先构造整个列表(这将致使无限循环),再将整个列表传递给 head
.这个列表的其他部分跟本没有被构造,由于它们对继续推动计算毫无心义.
当咱们引入 tail
函数时,Haskell被迫进一步构造列表,可是又一次仅仅构造了知足下一次计算所需的列表.同时,一旦计算结束,列表(未完成的)被丢弃了.
这里是一些部分计算无限列表的Haskell代码:
Prelude> let x = [1..] Prelude> let y = [2,4 ..] Prelude> let z = [3,6 ..] Prelude> head (tail (tail (zip3 x y z))) (3,6,9)
zip
函数将全部列表压缩在一块儿,以后抓取尾部的尾部的头部.又一次,Haskell没有发生任何问题,仅仅构造了计算所需的列表.咱们能够将Haskell运行时"消耗"这些无限列表的过程可视化:
图46: Haskell消耗一些无限列表
虽然咱们将Haskell运行时画为一个简单的循环,它可能被多线程实现(而且极可能若是你使用GHC版本的Haskell).但这幅图的关键点在于它十分像一个 reactor
循环,消耗从网络套接字传来的数据片断.
你能够把异步I/O和 reactor
模式视为一种有限形式的惰性计算.异步I/O的格言是:"仅仅推动你所拥有的数据".同时惰性计算的格言是:"仅仅推动你所需的数据".进一步,一个惰性计算语言在任何地方都使用这个格言,并不只仅是有限范围的I/O.
但关键点在于,对于惰性计算语言,作异步I/O没什么大不了的. 编译器和运行时已经被设计为一点一点地处理数据结构,于是惰性地处理到来的I/O数据流是标准问题. 如此Haskell运行时,就像Erlang运行时,简单地集成异步I/O为套接字抽象的一部分. 咱们以实现一个Haskell诗歌客户端来展现这个概念.
咱们第一个Haskell诗歌客户端位于 haskell-client-1/get-poetry.hs. 同Erlang同样,咱们直接给出了完成版的客户端,若是你但愿学习更多,咱们指出进一步阅读的参考.
Haskell一样支持轻量级线程或进程,尽管它们不是Haskell的核心,咱们的Haskell客户端为每首须要下载的诗歌建立一个进程.关键函数是 runTask,它链接到一个套接字而且以轻量级线程启动 getPoetry 函数.
在这个代码中,你将看到许多类型定义. Haskell,不像Python和Erlang,是静态类型的.咱们没有为每一个变量定义类型由于Haskell能够自动地推断没有显示定义的变量(或者报告错误若是不能推断).许多函数包含IO类型(技术上叫单子)由于Haskell要求咱们将有反作用的代码从纯函数中干净地分离(如,执行I/O的代码).
getPoetry 函数包含以下行:
poem <- hGetContents h
看起来像从句柄一次读入整首诗(如TCP套接字).可是Haskell,像往常同样,是惰性的.Haskell运行时包含一个或更多实际线程,它们在一个选择循环中执行异步I/O,如此便保存了惰性处理I/O流的可能性.
仅仅为说明异步I/O正在进行,咱们引入一个"回调"函数, gotLine,它为诗歌的每一行打印一些任务信息.但这不是一个真正的回调函数,不管咱们用不用它程序都会使用异步I/O.甚至叫它"gotLine"反映了一个必要的语言思惟,它是Haskell程序外的一部分.不管怎样,咱们将一点点清扫它,但先使Haskell客户端运转起来.
启动一些慢诗歌服务器:
python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt python blocking-server/slowpoetry.py --port 10002 poetry/science.txt python blocking-server/slowpoetry.py --port 10003 poetry/ecstasy.txt --num-bytes 30
如今编译Haskell客户端:
cd haskell-client-1/ ghc --make get-poetry.hs
这将建立一个二进制 get-poetry.最后,针对咱们的服务器运行客户端:
/get-poetry 10001 10002 1000
你将看到以下输出:
Task 3: got 12 bytes of poetry from localhost:10003 Task 3: got 1 bytes of poetry from localhost:10003 Task 3: got 30 bytes of poetry from localhost:10003 Task 2: got 20 bytes of poetry from localhost:10002 Task 3: got 44 bytes of poetry from localhost:10003 Task 2: got 1 bytes of poetry from localhost:10002 Task 3: got 29 bytes of poetry from localhost:10003 Task 1: got 36 bytes of poetry from localhost:10001 Task 1: got 1 bytes of poetry from localhost:10001 ...
输出与前一个异步客户端有点不一样,由于咱们只打印一行而不是任意块的数据.但,你能够清楚地看到,客户端是从全部服务器一块儿处理数据,而不是一个接一个.你一样能够注意到客户端当即打印第一首完成的诗,不等其余还在继续处理的诗.
好了,让咱们清除还剩下的一点讨厌东西而且发布一个仅仅抓取诗歌而不介意任务序号的新版本.它位于 haskell-client-2/get-poetry.hs. 注意它短多了,对于每一个服务器,仅仅链接到套接字,抓取全部数据,以后将其发送回去.
OK,让咱们编译新的客户端:
cd haskell-client-2/ ghc --make get-poetry.hs
针对相同的诗歌服务器组运行它:
./get-poetry 10001 10002 10003
最终,你将看到屏幕上出现每首诗的文字.
你将注意到每一个服务器同时向客户端发送数据.更重要的,客户端以最快速度打印出第一首诗的每一行,而不去等待其他的诗,甚至当它正在处理其它两首诗.以后它快速地打印出以前积累的第二首诗.
同时这全部发生的一切都不须要咱们作什么.这里没有回调,没有传来传去的消息,仅仅是一个关于咱们但愿程序作什么的简洁地描述,并且不多须要告诉它应该怎样作.其他的事情都是由Haskell编译器和运行时处理的.漂亮!
从Twisted到Erlang以后到Haskell,咱们能够看到一个平行的移动,从前景到背景逐步深刻异步编程背后的思想.在Twisted中,异步编程是其存在的核心激励理念. Twisted实现做为一个与Python分离的框架(Python缺少核心的异步抽象如轻量级线程),将异步模型置于首位与核心,当你用Twisted写程序时.
在Erlang中,异步对于程序员仍然是可见的,但细节成为语言材料的一部分和运行时系统,造成一个抽象使得异步消息在同步进程之间交换.
最后,在Haskell中,异步I/O仅仅是运行时中的另外一个技术,大部分对于程序员是不可见的,由于提供惰性计算是Haskell的中心理念.
对于以上状况,咱们尚未介绍任何深邃的思想.咱们仅仅指出许多而且有趣的异步模型出现的地方,这种模型能够被多种方式表达.
若是任何这些激起你对Haskell的兴趣,那么咱们建议"Real World Haskell"继续你的学习.这本书是介绍语言学习的典范.
同时虽然我没有读过它,我却据说"Learn You a Haskell"的饱受赞誉.
如今到告终束探索Twisted以外异步系统的时刻,而且完成了本系列的倒数第二部分. 在":doc:`p22`"中,咱们将作一个总结,以及建议一些学习Twisted的方法.
互相对比Twisted,Erlang和Haskell客户端.
修改Haskell客户端来处理链接诗歌服务器的失败,以便它们可以下载全部的可以下载的诗歌并为那些不能下载的诗歌输出合理的错误消息.
写Haskell版本的对应Twisted中的诗歌服务器.