使用Haskell进行非阻塞IO

5

可能是重复问题:
Haskell 对 Node.js 的回应是什么?
如何在 Haskell 中监视多个文件/套接字以变为可读/可写?

是否有可能编写一个Haskell程序,像nodejs一样以非阻塞的方式执行IO操作?

例如,我想从一个远程数据库中获取10条记录,因此我想同时发出10个请求,并在结果可用时返回此集合。 IO单子不会有所帮助,因为单子明确地使用绑定序列化计算。 我认为传递样式的延续,在其中传递您想要下一个的计算具有相同的问题,再次序列化计算。 我不想使用线程,我正在寻找另一种解决方案。 这是可能的吗?


2
当你说你不想使用线程时,如果你不需要自己管理线程,使用一个基于线程实现的库是否可以接受? - John L
3
你应该说出你不喜欢线程的原因,而不是只说你不想使用它们。 - Daniel Wagner
6
需要翻译的内容:(我应该指出Haskell的线程处理与操作系统线程相比非常轻量级,这是现有Web服务器框架能够很好扩展的原因之一。)Haskell对线程的处理方式非常轻量级,与操作系统的线程相比要简单得多,这也是现有的Web服务器框架能够很好地进行扩展的原因之一。 - AndrewC
2
5秒钟的谷歌搜索提示我,node.js的非阻塞操作是使用线程实现的。因此,如果您想要实现类似的功能,需要涉及线程。 - Ben
4
没错,我想这可能是你的抱怨。Haskell线程可以扩展到数十万范围,所以只需使用它们!(这也是为什么我建议你查看我提出的另一个与你有相同疑虑的StackOverflow问题。) - Daniel Wagner
显示剩余7条评论
2个回答

21

Haskell线程非常轻量级。此外,GHC的IO单子大部分时间使用事件驱动调度,这意味着普通的Haskell代码就像续体传递样式的node.js代码(只是编译成本机代码并在多个CPU上运行...)

你的例子很简单

import Control.Concurrent.Async

--given a list of requests
requests :: [IO Foo]

--you can run them concurrently
getRequests :: IO [Foo]
getRequests = mapConcurrently id requests

Control.Concurrent.Async 可能是您寻找的关于future的库。在Haskell中,仅使用数千个(普通)线程不应该出现问题。我从未编写过使用数百万个IO线程的代码,但我猜您唯一的问题可能是与内存有关的。


我接受这个解决方案,但它并没有真正回答我所提出的问题。是否有可能设计一种控制结构,允许“单线程”执行非阻塞并发编程(带有某些底层线程池)?通过“单线程”,我的意思是确保只有一个线程在并发执行(所有其他线程都被阻塞等待IO),因此可以使用常规IORefs而无需同步/阻塞。 - mmaroti
@mmaroti 嗯,一个做法是构建一个简单的monad,其中包含引用(使用IO引用实现)和FFI操作/系统调用,但由类型系统分为两个宇宙(不难做到),然后每当您想要在带有引用的monad中执行FFI调用时,您编写的代码相当于 async foo >>= unsafeInterleaveIO . wait。这将为您提供一种直接的编程风格,并以异步方式执行所有IO,但具有惰性IO的所有缺点。 - Philip JF
嗯,我得查一下这个。我对"懒惰IO"的弊端没有问题,因为我大部分想做的事情都是"功能性的":从数据库(如git)读取不可变数据。 - mmaroti

8

为了补充关于Control.Concurrent.Async的评论,这里提供了一个使用async包的示例。

import Network.HTTP.Conduit
import Control.Concurrent.Async

main = do
    xs <- mapM (async . simpleHttp) [ "www.stackoverflow.com"
                                    , "www.lwn.net"
                                    , "www.reddit.com/r/linux_gaming"]
    [so,lwn,lg] <- mapM wait xs
    -- parse these how ever you'd like

因此,在上述代码中,我们为三个不同的网站定义了三个HTTP GET请求,异步地启动这些请求,并在所有三个请求完成后继续执行。


2
线程化代码与单线程事件驱动的非阻塞代码不是同一回事。(我知道nodejs在内部使用线程池,但这不是重点)。您必须使用MVars在线程之间进行通信,这涉及到同步和更大规模的事务。然而,对于事件,如果我不调用需要回调的任何内容,我知道没有其他东西会修改程序状态。您的解决方案使用“等待”,这将导致阻塞,因此任何调用此类异步方法的调用者都需要放在单独的线程中才能继续:每个方法调用一个线程,对吗? - mmaroti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接