Ладно, здесь есть что распаковать. Во-первых, давайте обсудим ваш конкретный пример кода. Правильный способ написать свой 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
заблокированы при вводе-выводе и чистом вычислении соответственно. (В threadDelay
нет ничего особенного - это могло быть любое действие ввода-вывода, такое как доступ к базе данных, чтение большого файла, проксирование на другой HTTP-сервер или что-то еще.) Вы можете продолжать запускать несколько дополнительных запросов для fast
, slow
и pure
, а медленные будут пыхтеть в фоновом режиме, пока сервер продолжает принимать больше запросов. (Вычисление pure
немного отличается от вычисления slow
- оно будет блокироваться только в первый раз, все потоки, ожидающие его, вернут ответ сразу, а последующие запросы будут быстрыми. Если мы обманом заставили Haskell пересчитать его для каждый запрос, или если бы он действительно зависел от некоторой информации, предоставленной в запросе, как это могло бы иметь место на более реалистичном сервере, тем не менее, он будет действовать более или менее как вычисление slow
.)
Здесь вам не нужен какой-либо обратный вызов, и вам не нужно, чтобы основной поток «ждал» результата. Потоки, которые разветвляются Скотти для обработки каждого запроса, могут выполнять любые необходимые вычисления или операции ввода-вывода, а затем возвращать ответ напрямую клиенту, не затрагивая другие потоки.
Более того, если вы не скомпилировали этот сервер с -threaded
и не указали количество потоков> 1 либо во время компиляции, либо во время выполнения, он работает только в одном потоке ОС. Таким образом, по умолчанию он делает все это в один поток ОС автоматически!
Во-вторых, в Скотти нет ничего особенного. Вы должны думать о среде выполнения Haskell как о предоставлении уровня многопоточной абстракции поверх механизма потоков ОС, а потоки ОС - это деталь реализации, о которой вам не нужно беспокоиться (ну, за исключением необычных ситуаций, например, если вы повторное взаимодействие с внешней библиотекой, которая требует, чтобы определенные вещи происходили в определенных потоках ОС).
Итак, все потоки Haskell, даже «основной» поток, зеленые и выполняются поверх виртуальной машины, которая будет нормально работать поверх одного потока ОС, независимо от того, сколько зеленых потоков блокируется по какой-либо причине. .
Поэтому типичный шаблон для написания обработчика асинхронных запросов:
loop :: IO ()
loop = do
req <- getRequest
forkIO $ handleRequest req
loop
Обратите внимание, что здесь нет необходимости в обратном вызове. Функция handleRequest
выполняется в отдельном зеленом потоке для каждого запроса, который может выполнять длительные чистые вычисления с привязкой к ЦП, блокировать операции ввода-вывода и все, что необходимо, и потоку обработки не нужно передавать результат обратно в основную поток, чтобы, наконец, обработать запрос. Он может просто сообщить результат напрямую клиенту.
Scotty в основном построен на этом шаблоне, поэтому он автоматически отправляет несколько запросов, не требуя обратных вызовов или блокировки потоков ОС.
person
K. A. Buhr
schedule
18.08.2018
MVar
s илиTVar
s (или любой другой примитив параллелизма), как вы делали выше, и не бойтесь вызыватьforkIO
много раз (даже для коротких задач), если вам нужно больше параллелизма. - person chi   schedule 18.08.2018takeMVar
). Это и так бессмысленно. Вместо этого это может быть полезно, если исходный поток что-то сделал послеforkIO
и передtakeMVar
. - person chi   schedule 18.08.2018