跳转至内容

Haskell/并发

来自 Wikibooks,开放世界中的开放书籍

Haskell 中的并发主要通过 Haskell 线程实现。Haskell 线程是用户空间线程,在运行时实现。与操作系统线程相比,Haskell 线程在时间和空间效率上都更高。除了传统的同步原语(如信号量)之外,Haskell 还提供软件事务内存,这极大地简化了对共享内存的并发访问。

并发相关的模块是 Control.Concurrent.* 和 Control.Monad.STM。

你什么时候需要它?

[编辑 | 编辑源代码]

也许比 **何时** 更重要的是 **何时不**。Haskell 中的并发不用于利用多个处理器核心;你需要另一个东西,“并行”,来做到这一点。相反,并发用于当单个核心必须在不同事物之间分配注意力时,通常是 I/O。

例如,考虑一个简单的“静态” web 服务器(即只提供静态内容,如图像)。理想情况下,这样的 web 服务器应该消耗很少的处理资源;相反,它必须能够尽可能快地传输数据。瓶颈应该是 I/O,你可以在这个问题上投入更多硬件。因此,你必须能够在多个连接之间有效地利用单个处理器核心。

在用 C 语言编写的此类 web 服务器中,你将使用一个围绕 select() 在每个连接和监听套接字上进行的大循环。每个打开的连接都会附加一个数据结构,指定该连接的状态(即接收 HTTP 头,解析它,发送文件)。这样的循环很难且容易出错,难以手工编写。但是,使用并发 Haskell,你就可以编写一个更小的循环,只专注于监听套接字,它会为每个接受的连接生成一个新的“线程”。然后,你可以在 IO 幺半群中编写一个新的“线程”,它依次接收 HTTP 头、解析它并发送文件。

在内部,Haskell 编译器将把线程的生成转换为分配一个小的结构,指定“线程”的状态,与你在 C 中定义的数据结构相一致。然后,它将把各个线程转换为一个大的循环。因此,虽然你编写代码时就像每个线程都是独立的,但编译器会在内部将其转换为围绕 select() 或在你的系统上最佳的任何其他替代方案进行的大循环。

示例:并行下载文件

downloadFile :: URL -> IO ()
downloadFile = undefined

downloadFiles :: [URL] -> IO ()
downloadFiles = mapM_ (forkIO . downloadFile)

软件事务内存

[编辑 | 编辑源代码]

软件事务内存 (STM) 是一种机制,允许对内存进行类似于数据库事务的事务操作。它在多线程环境中编程时极大地简化了对共享资源的访问。通过使用 STM,你不再需要依赖锁定。

要使用 STM,你必须导入 Control.Concurrent.STM。要进入 STM 幺半群,使用 atomically 函数。STM 提供了不同的原语(TVarTMVarTChanTArray),可用于通信。

华夏公益教科书