Как дождаться завершения forM_ при использовании TVar?

Я пишу функцию, в которой я обрабатываю список, используя forM_, и добавляю результат в список TVar:

import Control.Concurrent.STM
import Control.Concurrent.STM.TVar
import Control.Concurrent (forkIO)
import Control.Monad (forM_)

insert :: a -> [a] -> [a]
insert = (:) -- stub

my_func_c :: (a -> a) -> [a] -> IO [a]
my_func_c my_function arr = do

    res_var <- atomically $ newTVar ([]::[a])

    forkIO $ forM_ arr $ \x -> atomically $ do
        let y = id $! my_function x
        modifyTVar res_var (insert y)

    atomically $ readTVar res_var

Результат всегда пустой, если я скомпилирую его с помощью -threaded. Как можно дождаться завершения потоков? Я не могу использовать MVar или Async. Я должен решить эту проблему, используя TVar или другие структуры данных, основанные на TVar.


person Iter Ator    schedule 28.04.2017    source источник


Ответы (1)


Идиоматическим решением было бы использовать TMVar с:

my_func_c :: (a -> a) -> [a] -> IO [a]
my_func_c my_function arr = do
    res_var <- atomically $ newTVar []
    finished <- atomically $ newEmptyTMVar

    forkIO $ do
        forM_ arr $ \x -> atomically $ do
            let y = id $! my_function x
            modifyTVar res_var (insert y)
        atomically $ putTMVar finished ()

    atomically $ takeTMVar finished >> readTVar res_var

но если вам действительно разрешено использовать только TVars, вы можете смоделировать TMVar () с TVar Bool:

my_func_c :: (a -> a) -> [a] -> IO [a]
my_func_c my_function arr = do
    res_var <- atomically $ newTVar []
    finished <- atomically $ newTVar False

    forkIO $ do
        forM_ arr $ \x -> atomically $ do
            let y = id $! my_function x
            modifyTVar res_var (insert y)
        atomically $ writeTVar finished True

    atomically $ waitTVar finished >> readTVar res_var

waitTVar :: TVar Bool -> STM ()
waitTVar tv = do
    finished <- readTVar tv
    unless finished retry

Под капотом TMVar a — это просто TVar (Maybe a) (то есть TMVar () ~ TVar (Maybe ()) ~ TVar Bool) с takeTMVar выполняет readTVar и retry, поэтому два приведенных выше решения практически эквивалентны.

person Cactus    schedule 28.04.2017
comment
Почему writeTVar finished True не требуется в каждом вызове функции forM_? Почему он снаружи forM_? - person Iter Ator; 28.04.2017
comment
Дело в том, что вы writeTVar finished True / putTMVar finished когда полностью закончите, то есть после того, как будут вычислены все результаты. Обратите внимание, что это также будет работать, если внутри forM_ все элементы обрабатываются в отдельном потоке (это то, что я предполагаю, что вы захотите сделать в своем реальном приложении) - person Cactus; 28.04.2017