Haskell/并发
读者已经将本章节识别为未完成的草稿或提纲。 您可以帮助完善工作,或者您可以向项目室寻求帮助。 |
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 提供了不同的原语(TVar
、TMVar
、TChan
和 TArray
),可用于通信。