1

I'm writing a function, where I process a list using forM_, and append the result to a TVar list:

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

The result is always empty, if I compile it with -threaded. How is it possible to wait for the threads to finish? I can not use MVar or Async. I have to solve this problem using TVar, or other data structures which are based on TVar

Cactus
  • 27,075
  • 9
  • 69
  • 149
Iter Ator
  • 8,226
  • 20
  • 73
  • 164

1 Answers1

3

The idiomatic solution would be to use TMVars:

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

but if you are really only allowed to use TVars, you can simulate a TMVar () with a 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

Under the hood, TMVar a is just a TVar (Maybe a) (so TMVar () ~ TVar (Maybe ()) ~ TVar Bool) with takeTMVar doing a readTVar and a retry, so the above two solutions are operationally completely equivalent.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • Why isn't `writeTVar finished True` required in each forM_ function call? Why is it outside `forM_`? – Iter Ator Apr 28 '17 at 09:17
  • The point is that you `writeTVar finished True` / `putTMVar finished ` when you are fully finished, i.e. after *all* results have been computed. Note that this would also work if inside `forM_`, all elements are processed in a separate thread (which is what I assume you will want to do in your real app) – Cactus Apr 28 '17 at 09:23