Haskell 中的阻塞线程
Blocking Threads in Haskell
我开始使用 Haskell 进行异步编码,现在我正在使用 forkIO
创建一个绿色线程(对吗?是绿色线程吗?)然后我'我使用 MVar
从新线程到主线程进行通信,一旦我完成并且我有值。这是我的代码:
responseUsers :: ActionM ()
responseUsers = do emptyVar <- liftAndCatchIO $newEmptyMVar
liftAndCatchIO $ forkIO $ do
users <- getAllUsers
putMVar emptyVar users
users <- liftAndCatchIO $ takeMVar emptyVar
json (show users)
阅读后 MVar
class 我可以看到是一个阻塞线程 class 如果 MVar 为空,则阻塞线程直到被填充。
我来自 Scala
,在其他 do avoid block 中,我们在 Future 对象中有回调的概念,其中线程 A
可以创建线程 B
并接收Future
.
然后在回调函数 onComplete
中订阅,一旦线程 B
完成该值,它将被调用。
但在那段时间里,线程 A
没有被阻塞,可以重新用于其他操作。
例如在我们的 Http 服务器框架中,如 Vertx
或 Grizzly
通常配置为具有少量 OS 线程(4-8),因为它们永远不会被阻止。
难道我们在Haskell中还有另一个纯粹的无阻塞机制吗?
此致
好的,这里有很多东西要打开。首先,让我们讨论您的具体代码示例。为 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
link return,即使 slow
和 pure
link分别在 IO 和纯计算上被阻塞。 (threadDelay
没有什么特别的——它可能是任何 IO 操作,比如访问数据库或读取大文件或代理到另一个 HTTP 服务器等等。)你可以继续为 [=17 发起多个额外的请求=]、slow
和 pure
,当服务器继续接受更多请求时,速度较慢的将在后台突然消失。 (pure
计算与 slow
计算略有不同——它只会在第一次时阻塞,所有等待它的线程都会立即 return 一个答案,随后的请求会很快。如果我们欺骗 Haskell 为每个请求重新计算它,或者如果它实际上取决于请求中提供的某些信息(在更现实的服务器中可能是这种情况),它的行为或多或少会像slow
计算,不过。)
这里不需要任何类型的回调,也不需要主线程 "wait" 结果。由 Scotty 分叉处理每个请求的线程可以执行任何需要的计算或 IO activity,然后 return 直接响应客户端,而不会影响任何其他线程。
此外,除非您使用 -threaded
编译此服务器并在编译或 运行 时提供大于 1 的线程数,否则 它仅 运行s在一个 OS 线程中。 因此,默认情况下,它会在一个 OS 线程中自动执行所有这些操作!
其次,这实际上并不是 Scotty 的任何特别之处。您应该将 Haskell 运行time 视为在 OS 线程机制之上提供线程抽象层,而 OS 线程是您不知道的实现细节不必担心(好吧,除了在不寻常的情况下,比如如果你正在与一个外部库接口,它需要在某些 OS 线程中发生某些事情)。
所以,所有 Haskell 线程,甚至 "main" 线程,都是绿色的,并且 运行 在某种虚拟机之上 运行在单个 OS 线程之上很好,无论有多少绿色线程出于某种原因阻塞。
因此,编写异步请求处理程序的典型模式是:
loop :: IO ()
loop = do
req <- getRequest
forkIO $ handleRequest req
loop
请注意,此处不需要回调。 handleRequest
函数 运行s 在每个请求的单独的绿色线程中,可以执行 long-运行ning 纯 CPU-bound 计算,阻塞 IO 操作,以及任何其他需要,并且处理线程不需要将结果传回主线程以最终为请求提供服务。它可以直接将结果传达给客户端。
Scotty 基本上是围绕这种模式构建的,因此它会自动分派多个请求,而无需回调或阻塞 OS 个线程。
我开始使用 Haskell 进行异步编码,现在我正在使用 forkIO
创建一个绿色线程(对吗?是绿色线程吗?)然后我'我使用 MVar
从新线程到主线程进行通信,一旦我完成并且我有值。这是我的代码:
responseUsers :: ActionM ()
responseUsers = do emptyVar <- liftAndCatchIO $newEmptyMVar
liftAndCatchIO $ forkIO $ do
users <- getAllUsers
putMVar emptyVar users
users <- liftAndCatchIO $ takeMVar emptyVar
json (show users)
阅读后 MVar
class 我可以看到是一个阻塞线程 class 如果 MVar 为空,则阻塞线程直到被填充。
我来自 Scala
,在其他 do avoid block 中,我们在 Future 对象中有回调的概念,其中线程 A
可以创建线程 B
并接收Future
.
然后在回调函数 onComplete
中订阅,一旦线程 B
完成该值,它将被调用。
但在那段时间里,线程 A
没有被阻塞,可以重新用于其他操作。
例如在我们的 Http 服务器框架中,如 Vertx
或 Grizzly
通常配置为具有少量 OS 线程(4-8),因为它们永远不会被阻止。
难道我们在Haskell中还有另一个纯粹的无阻塞机制吗?
此致
好的,这里有很多东西要打开。首先,让我们讨论您的具体代码示例。为 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
link return,即使 slow
和 pure
link分别在 IO 和纯计算上被阻塞。 (threadDelay
没有什么特别的——它可能是任何 IO 操作,比如访问数据库或读取大文件或代理到另一个 HTTP 服务器等等。)你可以继续为 [=17 发起多个额外的请求=]、slow
和 pure
,当服务器继续接受更多请求时,速度较慢的将在后台突然消失。 (pure
计算与 slow
计算略有不同——它只会在第一次时阻塞,所有等待它的线程都会立即 return 一个答案,随后的请求会很快。如果我们欺骗 Haskell 为每个请求重新计算它,或者如果它实际上取决于请求中提供的某些信息(在更现实的服务器中可能是这种情况),它的行为或多或少会像slow
计算,不过。)
这里不需要任何类型的回调,也不需要主线程 "wait" 结果。由 Scotty 分叉处理每个请求的线程可以执行任何需要的计算或 IO activity,然后 return 直接响应客户端,而不会影响任何其他线程。
此外,除非您使用 -threaded
编译此服务器并在编译或 运行 时提供大于 1 的线程数,否则 它仅 运行s在一个 OS 线程中。 因此,默认情况下,它会在一个 OS 线程中自动执行所有这些操作!
其次,这实际上并不是 Scotty 的任何特别之处。您应该将 Haskell 运行time 视为在 OS 线程机制之上提供线程抽象层,而 OS 线程是您不知道的实现细节不必担心(好吧,除了在不寻常的情况下,比如如果你正在与一个外部库接口,它需要在某些 OS 线程中发生某些事情)。
所以,所有 Haskell 线程,甚至 "main" 线程,都是绿色的,并且 运行 在某种虚拟机之上 运行在单个 OS 线程之上很好,无论有多少绿色线程出于某种原因阻塞。
因此,编写异步请求处理程序的典型模式是:
loop :: IO ()
loop = do
req <- getRequest
forkIO $ handleRequest req
loop
请注意,此处不需要回调。 handleRequest
函数 运行s 在每个请求的单独的绿色线程中,可以执行 long-运行ning 纯 CPU-bound 计算,阻塞 IO 操作,以及任何其他需要,并且处理线程不需要将结果传回主线程以最终为请求提供服务。它可以直接将结果传达给客户端。
Scotty 基本上是围绕这种模式构建的,因此它会自动分派多个请求,而无需回调或阻塞 OS 个线程。