我开始使用Haskell进行异步编码,现在我正在使用forkIO
来创建一个绿色线程(是否正确?是绿色线程吗?),然后我使用{{1 }}完成后,我就有了从新线程到主线程的通信。这是我的代码:
MVar
在读取responseUsers :: ActionM ()
responseUsers = do emptyVar <- liftAndCatchIO $newEmptyMVar
liftAndCatchIO $ forkIO $ do
users <- getAllUsers
putMVar emptyVar users
users <- liftAndCatchIO $ takeMVar emptyVar
json (show users)
类之后,我可以看到是一个块线程类,其中如果MVar为空,则该块直到被填充为止。
我来自MVar
,在其他情况下,我们避免阻塞,在Future对象中有回调的概念,其中线程Scala
可以创建线程A
并接收{ {1}}。
然后订阅回调函数B
,一旦线程Future
的值结束,它将被调用。
但是在这段时间内,线程onComplete
没有被阻塞,可以重新用于其他操作。
例如在我们的Http服务器框架中,例如B
或A
通常配置为具有少量OS线程(4-8),因为它们永远都不会被阻塞。
我们在Haskell中是否还有另一种完全没有阻塞的机制?
致谢
答案 0 :(得分:4)
好的,这里有很多东西要解压。首先,让我们讨论您的特定代码示例。为Scotty编写responseUsers
处理程序的正确方法是:
responseUsers :: ActionM ()
responseUsers = do
users <- getAllUsers
json (show users)
即使getAllUsers
需要花一天半的时间运行,并且一百个客户端全部一次发出getAllUsers
请求,其他都不会阻塞,并且您的Scotty服务器将继续处理请求。要查看此信息,请考虑以下服务器:
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Control.Concurrent
import Control.Monad.IO.Class
import qualified Data.Text.Lazy as T
main = scotty 8080 $ do
get "/fast" $ html "<h1>Fast Response</h1><p>I'm ready!"
get "/slow" $ liftIO (threadDelay 30000000) >> html "<h1>Slow</h1><p>Whew, finally!"
get "/pure" $ html $ "<h1>Answer</h1><p>The answer is "
<> (T.pack . show . sum $ [1..1000000000])
如果您对此进行编译并启动,则可以打开多个浏览器标签以进行以下操作:
http://localhost:8080/slow
http://localhost:8080/pure
http://localhost:8080/fast
,您将看到fast
链接立即返回,即使slow
和pure
链接分别在IO和纯计算上被阻塞。 (threadDelay
没什么特别的-它可能是任何IO操作,例如访问数据库或读取大文件或代理到其他HTTP服务器等。)您可以继续为{{1 }},fast
和slow
,当服务器继续接受更多请求时,速度较慢的请求将在后台消失。 (pure
计算与pure
计算略有不同-它只会在第一次出现时阻塞,等待它的所有线程都将立即返回一个答案,随后的请求将很快。如果我们诱使Haskell为每个请求重新计算它,或者如果它实际上依赖于请求中提供的某些信息(在更现实的服务器中可能就是这种情况),则其行为或多或少类似于slow
计算)
您在这里不需要任何回调,也不需要主线程“等待”结果。 Scotty分叉来处理每个请求的线程可以执行所需的任何计算或IO活动,然后将响应直接返回给客户端,而不会影响任何其他线程。
此外,除非您使用slow
编译此服务器并在编译或运行时提供线程计数> 1,否则它仅在一个OS线程中运行。 ,它会自动在单个OS线程中完成所有操作!
第二,这实际上与Scotty无关。您应该将Haskell运行时视为在OS线程机制之上提供了一个线程抽象层,并且OS线程是您不必担心的实现细节(很好,除非在特殊情况下,例如重新与需要在某些OS线程中发生某些事情的外部库进行交互。
因此,所有Haskell线程,甚至是“主”线程,都是绿色的,并且在某种虚拟机上运行,无论有多少绿色线程阻塞,该虚拟机都可以在单个OS线程上正常运行无论出于什么原因。
因此,编写异步请求处理程序的典型模式是:
-threaded
请注意,这里不需要回调。 loop :: IO ()
loop = do
req <- getRequest
forkIO $ handleRequest req
loop
函数在每个可以执行长时间运行的纯CPU绑定计算,阻止IO操作以及其他所需操作的请求的单独绿色线程中运行,并且处理线程不需要传递结果回到主线程,以便最终处理请求。它可以直接将结果传达给客户端。
Scotty基本上是围绕这种模式构建的,因此它可以自动调度多个请求,而无需回调或阻塞OS线程。