Haskell对Node.js的响应是什么?


217

我相信Erlang社区不会羡慕Node.js,因为它本身就进行非阻塞I / O,并具有将部署轻松扩展到一个以上处理器(Node.js甚至没有内置的功能)的方法。有关更多详细信息,请访问http://journal.dedasys.com/2010/04/29/erlang-vs-node-jsNode.js或Erlang

那Haskell呢?Haskell是否可以提供Node.js的某些好处,即一种避免使用I / O而不使用多线程编程的干净解决方案?


Node.js有很多吸引人的地方

  1. 事件:无线程操作,程序员仅提供回调(如Snap框架中一样)
  2. 回调保证在单个线程中运行:不可能出现竞争条件。
  3. 漂亮又简单的UNIX友好API。奖励:出色的HTTP支持。DNS也可用。
  4. 默认情况下,每个I / O都是异步的。这样可以更轻松地避免锁定。但是,回调中过多的CPU处理会影响其他连接(在这种情况下,任务应拆分为较小的子任务并重新计划)。
  5. 客户端和服务器端使用相同的语言。(但是,我认为这一点没有太大价值。jQuery和Node.js共享事件编程模型,但其余部分却大不相同。我只是看不到如何在服务器端和客户端之间共享代码。在实践中很有用。)
  6. 所有这些都包装在一个产品中。

17
我认为您应该在程序员上问这个问题。
乔纳斯(Jonas)2010年

47
不包含一段代码并不会使它成为一个主观的问题。
gawi 2010年

20
我对node.js的了解不多,但是有一个问题令我震惊:您为什么觉得线程前景如此令人不愉快?线程应该是复用I / O的正确解决方案。我在这里广泛使用“线程”一词,包括Erlang的进程。也许您担心锁和可变状态?您不必那样做-如果对您的应用程序更有意义,则使用消息传递或事务。
西蒙·马洛

9
@gawi我认为这听起来并不容易编程-如果没有抢占,则必须应对饥饿和长时间等待的可能性。基本上,线程是Web服务器的正确抽象-无需处理异步I / O以及随之而来的所有困难,只需在线程中完成即可。顺便说一句,我在Haskell上写了一篇有关Web服务器的论文,您可能会发现它很有趣:haskell.org/~simonmar/papers/web-server-jfp.pdf
Simon Marlow 2010年

3
“保证回调可以在单个线程中运行:没有竞争条件。” 错误。您可以轻松在Node.js中获得竞争条件;只是假设一个I / O操作将在另一个操作和BOOM之前完成。什么确实不可能是一个特定种类的竞争条件,即不同步并发访问同一字节在内存中。
2015年

Answers:


219

好的,因此,在观看了@gawi指向我的一些node.js演示文稿之后,我可以进一步说明Haskell与node.js的比较。在演示中,Ryan描述了Green Threads的一些好处,但是接着说,他并不认为缺少线程抽象并不是一个缺点。我不同意他的立场,尤其是在Haskell的情况下:我认为线程提供的抽象对于使服务器代码更容易正确使用和更健壮至关重要。特别是:

  • 每个连接使用一个线程可使您编写表示与单个客户端进行通信的代码,而不是编写同时处理所有客户端的代码。可以这样想:一台处理带有线程的多个客户端的服务器看起来与处理一个客户端的服务器几乎相同;主要区别在于fork前者中有某个地方。如果您要实现的协议非常复杂,则同时管理多个客户端的状态机将变得非常棘手,而线程使您仅可以编写与单个客户端的通信脚本。该代码更容易正确使用,也易于理解和维护。

  • 单个OS线程上的回调是协作式多任务处理,与抢先式多任务处理相反,后者是线程获得的。协作式多任务处理的主要缺点在于,程序员负责确保没有饥饿。它失去了模块化:在一个地方犯一个错误,它会破坏整个系统。这确实是您不需要担心的事情,而抢占是简单的解决方案。而且,回调之间的通信是不可能的(这将导致死锁)。

  • 并发在Haskell中并不难,因为大多数代码是纯净的,因此构造时线程安全的。有简单的通信原语。与使用无限制副作用的语言相比,在Haskell中并发地射击自己要困难得多。


42
好的,所以我知道node.js是解决2个问题的解决方案:1-在大多数语言中并发性都很差,2-使用OS线程是可扩展的。Node.js解决方案是使用基于事件的并发(带有libev)以避免线程之间的通信,并避免OS线程的可伸缩性问题。由于纯度,Haskell没有问题1。对于#2,Haskell具有轻量级线程+事件管理器,最近在GHC中针对大型上下文对其进行了优化。另外,对于任何Haskell开发人员来说,使用Javascript都不是一个加分。对于使用Snap Framework的某些人来说,Node.js太糟糕了。
gawi 2010年

4
在大多数情况下,请求处理是一系列相互依赖的操作。我倾向于同意对每个阻塞操作使用回调可能很麻烦。为此,线程比回调更适合。
gawi 2010年

10
是的 而且,GHC 7中全新的I / O多路复用功能使在Haskell中编写服务器变得更好。
andreypopp 2010年

3
我的第一点对我(作为局外人)没有多大意义……在node.js中处理请求时,您的回调仅处理单个客户端。仅在扩展到多个流程时,管理状态才成为要担心的事情,即使那样,使用可用的库也很容易。
里卡多·托马西

12
这不是一个单独的问题。如果此问题是在Haskell中真正寻找适合该工作的最佳工具,还是检查Haskell中是否存在用于该工作的优秀工具,则需要挑战隐含的假设,即多线程编程将不合适,因为Haskell确实正如唐·斯图尔特(Don Stewart)所指出的那样,它们的线程截然不同。解释Haskell社区为什么还不嫉妒Node.js的答案是这个问题的主题。gawi的回答表明,这是对他的问题的适当回答。
AndrewC 2012年

154

Haskell是否可以提供Node.js的某些好处,即一种避免使用I / O而不使用多线程编程的干净解决方案?

是的,实际上事件和线程在Haskell中是统一的。

  • 您可以在显式轻量级线程中编程(例如,一台笔记本电脑上有数百万个线程)。
  • 要么; 您可以根据可扩展的事件通知以异步事件驱动的样式进行编程。

线程实际上是根据事件来实现的,并跨多个核心运行,具有无缝的线程迁移,具有记录的性能和应用程序。

例如

在32个内核上并发收集nbody

替代文字

在Haskell中,您既有事件也有线程,而且所有事件都在后台进行。

阅读描述实现的文章。


2
谢谢。我需要消化所有这些……这似乎是GHC特有的。我想没关系。Haskell语言是GHC可以编译的东西。以类似的方式,Haskell“平台”或多或少是GHC运行时。
gawi

1
@gawi:将其与所有其他捆绑在一起的软件包打包在一起,以便立即使用。这就是我在计算机科学课程中看到的相同图像;最棒的是,在Haskell中,在自己的程序中获得类似的出色结果并不难。
罗伯特·马赛奥利

1
嗨,唐,您认为在回答此类问题时,您可以链接到性能最佳(Warp)的haskell Web服务器吗?这是针对Node.js的非常相关的基准测试:yesodweb.com/blog/2011/03/…–
Greg Weber

4
从理论上讲。Haskell的“轻量级线程”并没有您想象的那么轻便。在epoll接口上注册回调要比调度所谓的绿色线程便宜得多,它们当然比OS线程便宜,但它们不是免费的。创建其中的100.000使用大约。350 MB的内存,需要一些时间。尝试与node.js建立100.000连接。没问题的。如果速度不快,那就太神奇了,因为ghc在后台使用了epoll,因此它们不能比直接使用epoll快。但是,使用线程接口进行编程非常好。
Kr0e

3
另外:新的IO管理器(ghc)使用具有(m log n)复杂度的调度算法(其中m是可运行线程数,n是线程总数)。Epoll的复杂度为k(k是可读/可写的fd's =的数量。因此ghc在所有复杂度上都具有O(k * m log n),如果您面临高流量连接,则不是很好。Node.js只是线性复杂度引起的通过epoll的,只是让我们不谈论Windows性能... Node.js的是更快,因为它使用IOCP。
Kr0e

20

首先,我不认为您认为node.js做正确的事情公开了所有这些回调。您最终以CPS(连续传递样式)编写程序,我认为完成该转换应该是编译器的工作。

事件:无线程操作,程序员仅提供回调(如Snap框架中一样)

因此,考虑到这一点,您可以根据需要使用异步样式进行编写,但是这样做会错过以高效的同步样式进行编写的功能,每个请求只有一个线程。Haskell在同步代码方面效率极高,尤其是与其他语言相比时。这是所有的事件。

回调保证在单个线程中运行:不可能出现竞争条件。

您仍然可以在node.js中有一个竞争条件,但这更加困难。

每个请求都在其自己的线程中。当您编写必须与其他线程进行通信的代码时,由于haskell的并发原语,使其变得线程安全非常简单。

漂亮又简单的UNIX友好API。奖励:出色的HTTP支持。DNS也可用。

看看黑客,自己看看。

默认情况下,每个I / O都是异步的(尽管有时这很烦人)。这样可以更轻松地避免锁定。但是,回调中过多的CPU处理会影响其他连接(在这种情况下,任务应拆分为较小的子任务并重新计划)。

您没有此类问题,ghc会将您的工作分配到实际的OS线程中。

客户端和服务器端使用相同的语言。(但是,我认为这一点没有太大价值。JQuery和Node.js共享事件编程模型,但其余部分却大不相同。我只是看不到如何在服务器端和客户端之间共享代码。在实践中很有用。)

Haskell不可能在这里赢球...对吗?再想一想,http://www.haskell.org/haskellwiki/Haskell_in_web_browser

所有这些都包装在一个产品中。

下载ghc,启动cabal。有一个程序包可以满足您的所有需求。


我只是在扮演恶魔的拥护者。所以,是的,我同意你的观点。除了客户端和服务器端语言统一。虽然我认为这在技术上是可行的,但我认为它最终不会取代当今所有的Javascript生态系统(JQuery和它的朋友)。尽管这是Node.js支持者提出的论据,但我认为这不是一个非常重要的论点。您是否真的需要在表示层和后端之间共享那么多代码?我们是否真的打算让程序员只知道一种语言?
gawi 2011年

真正的优势在于,您可以在服务器和客户端上渲染页面,从而使实时页面的创建更加容易。
dan_waterworth 2011年

@dan_waterworth准确,请参见流星derby.js
mb21

1
@gawi我们提供的生产服务在客户端和服务器之间共享85%的代码。这在社区中被称为通用JavaScript。我们正在使用React在服务器上动态呈现内容,以减少在客户端首次进行有用呈现的时间。虽然我知道您可以在浏览器中运行Haskell,但我不知道任何一组“通用Haskell”最佳实践,它们允许使用同一代码库进行服务器端和客户端渲染。
埃里克·埃利奥特

8

我个人认为Node.js和带有回调的编程是不必要的低级和不自然的事情。当良好的运行时(例如GHC中找到的运行时)可以为您处理回调并且效率很高时,为什么要使用回调进行编程?

同时,GHC运行时有了很大的改进:它现在具有一个称为MIO的“新IO管理器” ,其中“ M”代表多核。它建立在现有IO管理器的基础上,其主要目标是克服导致4个以上内核性能下降的原因。本文提供的性能数字令人印象深刻。见自己:

使用Mio,Haskell中的实际HTTP服务器可扩展到20个CPU内核,与使用以前版本的GHC的相同服务器相比,可将峰值性能提高到6.5倍。Haskell服务器的延迟也得到了改善:在中等负载下,与以前版本的GHC相比,预期响应时间减少了5.7倍

和:

我们还表明,借助Mio,McNettle(用Haskell编写的SDN控制器)可以有效地扩展到40多个内核,在一台机器上每秒可处理超过2000万个新请求,从而成为所有现有SDN控制器中最快的。

Mio已将其纳入GHC 7.8.1版本。我个人认为这是Haskell性能上的重要一步。比较以前的GHC版本和7.8.1编译的现有Web应用程序的性能将非常有趣。


6

恕我直言,事件是好的,但通过回调进行编程不是。

使Web应用程序的编码和调试特别特殊的大多数问题来自使它们具有可伸缩性和灵活性的原因。最重要的是HTTP的无状态性质。这增强了可导航性,但是在IO元素(在这种情况下为Web服务器)调用应用程序代码中的不同处理程序时,会带来控制反转。此事件模型(或更准确地说是回调模型)是一场噩梦,因为回调不共享变量范围,并且导航的直观视图也丢失了。防止用户来回导航时所有可能的状态更改以及其他问题非常困难。

可以说问题类似于事件模型可以正常工作的GUI编程,但是GUI没有导航和后退按钮。这增加了Web应用程序中可能的状态转换。试图解决这些问题的结果是重型框架具有复杂的配置,大量的魔术标识符没有引起问题的根源:回调模型及其固有的缺乏可变范围共享的特性,并且没有顺序,因此顺序必须通过链接标识符来构造。

存在基于顺序的框架,例如ocsigen(ocaml)海边(smalltalk)WASH(已停产,Haskell)和mflow(Haskell),这些框架在维持可导航性和REST功能的同时解决了状态管理问题。在这些框架中,程序员可以将导航表示为命令序列,其中程序在单个线程中发送页面并等待响应,变量在范围内,后退按钮自动工作。这样就固有地生成了更短,更安全,更具可读性的代码,其中程序员清楚地看到了导航。(警告:我是mflow的开发人员)


在node.js中,回调用于处理异步I / O,例如到数据库。您正在谈论的是一些不同的东西,尽管很有趣,但却无法回答问题。
罗宾·格林

你是对的。我希望用三年的时间来回答您的反对:github.com/transient-haskell
agocorona

Node现在支持异步功能,这意味着您可以编写实际上是异步的命令式代码。它在幕后使用诺言。
埃里克·埃利奥特

5

这个问题非常荒谬,因为1)Haskell已经以更好的方式解决了这个问题,并且2)Erlang的方式大致相同。这是针对节点的基准测试:http : //www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks

给Haskell 4个内核,它可以在一个应用程序中每秒处理10万个(简单)请求。Node不能做那么多,也不能跨内核扩展单个应用程序。而且您无需采取任何措施来获得此收益,因为Haskell运行时是非阻塞的。Erlang是运行时内置的非阻塞IO的唯一其他(相对通用)语言。


14
荒谬?问题不是“ Haskell有回应”,而是“ Haskell回应是什么”。在问这个问题时,GHC 7甚至没有发布,因此Haskell还没有“参与其中”(也许对于使用Snap这样的libev的框架除外)。除此之外,我同意。
gawi 2011年

1
我不知道在您发布此答案时是否如此,但是实际上,现在有节点模块允许节点应用程序轻松地跨内核扩展。此外,该链接还将单个内核上运行的node.js与4个内核上运行的haskell进行了比较。我希望看到它在更合理的配置下再次运行,但是可惜,github存储库已经消失了。
蒂姆·高

2
Haskell使用4个以上的内核会降低应用程序的性能。关于此问题有一篇论文,正在积极研究中,但仍然是一个问题。因此,在16台核心服务器上运行Node.js的16个实例最有可能比使用+ RTS -N16的单个ghc应用程序好得多,由于此运行时错误,它确实比+ RTS -N1慢。这是因为它们仅使用一个IOManager,当与许多OS线程一起使用时,IOManager的速度会降低。我希望他们会修复此错误,但此错误自从存在以来,所以我希望不大...
Kr0e

任何看过这个答案的人都应该意识到,Node可以轻松地在单个内核上处理10万个简单请求,并且在多个内核上扩展无状态Node应用程序也很容易。pm2 -i max path/to/app.js会根据可用内核自动扩展到最佳实例数。此外,默认情况下,Node也处于非阻塞状态。
埃里克·埃利奥特

1

1
这如何回答这个问题?
dfeuer

1
@dfeuer链接必须读为Snap Haskell Web Framework删除了libev,但我不知道为什么格式化失败。节点服务器运行时一开始就是关于Linux libev的,Snap Web FrameWork也是如此。Haskell与Snap就像ECMAscript与Node.js一样,因此Snap与Node.js一起发展的方式比Haskell更相关,在这种情况下,与ECMAscript相比,Haskell更加正确。
Chawathe Vipul S
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.