Вопросы о параллелизме и `до` - PullRequest
4 голосов
/ 11 апреля 2019

У меня есть следующее приложение Scotty:

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Web.Scotty
import Data.Monoid (mconcat)
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Control.Concurrent

main :: IO ()
main = do
  counter <- newTVarIO 0
  scotty 3000 $
    get "/:word" $ do
      liftIO $ threadDelay 1000000
      liftIO $ atomically $ do
        counter' <- readTVar counter
        writeTVar counter (counter' + 1)
      liftIO $ do
        counter' <- atomically (readTVar counter)
        print counter'
      beam <- param "word"
      html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

И я вызываю открытую конечную точку следующим образом (200 одновременных запросов):

wrk -c 200 -t 20 -d 10 http://127.0.0.1:3000/z

Я ожидал значение counter' для печати последовательно.Однако некоторые цифры отсутствуют, а некоторые дублируются (например, 147 там дважды, а 146 вообще нет).

Два вопроса:

  1. Единственный способ, которым это может произойти, я думаю, это то, что за вторым liftIO не обязательно следует третий liftIO.Это правильно?Или есть другое объяснение этому?

  2. Как я могу напечатать значение counter' во втором liftIO?Я не уверен, как разместить его между (или после) readTVar и writeTVar.

1 Ответ

2 голосов
/ 11 апреля 2019

Есть пара проблем с вашим кодом.Во-первых, как вы указали, между 2-м и 3-м liftIO с может произойти все что угодно (то есть между увеличением счетчика и его повторным чтением).Вы можете реструктурировать код следующим образом:

main :: IO ()
main = do
  counter <- newTVarIO 0
  scotty 3000 $
    get "/:word" $ do
      -- wrap IO in do-block to avoid repeating liftIO
      liftIO $ do
        threadDelay 1000000
        -- Remember the value instead of reading it again.
        value <- atomically $ do
          x <- readTVar counter
          let x' = x + 1
          writeTVar counter x'
          return x'
        print value
      beam <- param "word"
      html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

Это исправит пропущенные и дублированные номера.Тем не менее, результат все еще выглядит грязным из-за чередования print результатов.Это можно исправить, следуя предложению в Могу ли я убедиться, что Haskell выполняет атомарный ввод-вывод? :

main :: IO ()
main = do
  counter <- newTVarIO 0
  -- Create a lock for printing.
  lock <- newMVar ()
  scotty 3000 $
    get "/:word" $ do
      liftIO $ do
        threadDelay 1000000
        value <- atomically $ do
          x <- readTVar counter
          let x' = x + 1
          writeTVar counter x'
          return x'
        -- Hold a lock while printing.
        withMVar lock $ \_ -> print value
      beam <- param "word"
      html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

Это очищает вывод, но все равно не гарантирует, что числа будутпечататься в последовательности, поскольку между разделами atomically и withMVar может произойти все, что угодно.Когда я запустил его, как и ожидалось, вывод был в основном по порядку (номера от 1 до 2180), но с некоторыми исключениями.

Может быть способ выполнить инкремент и печать атомарно, но монада STM нене предназначен, чтобы сделать это легко.В частности, см. Все предупреждения об использовании небезопасного ввода-вывода с atomically.

...