I’d like to play tricks with forkProcess, where I want to clone my Haskell process, and then let both clones talk to each other (maybe using Cloud Haskell to send even closures around).
But I wonder how well that works with the GHC runtime. Does anyone have experience here?
The documenation for forkProcess says that no other threads are copied, so I assume all data used by other threads will then be garbage collected in the fork, which sounds good. But that means that finalizers will run in both clone, which may or may not be the right thing to do…
I assume I can’t just use it without worry; but are there rules I can follow that will make sure its use is safe?
But that means that finalizers will run in both clone, which may or may not be the right thing to do…
Finalizers are very rarely used in Haskell, and even where they are used, I would expect them to only have in-process effects. For example, a finalizer calls hClose on garbage-collected Handles if you forgot to do it yourself. This is easy to demonstrate: the following program fails with openFile: resource exhausted (Too many open files), but if you uncomment the pure (), the Handles get garbage-collected and the program completes successfully.
import Control.Concurrent
import Control.Monad
import System.IO
import System.Mem
main :: IO ()
main = do
rs <- replicateM 1000 $ do
threadDelay 1000 -- not sure why did is needed; maybe to give control back
-- to the OS, so it can recycle the file descriptors?
performGC
openFile "input" ReadMode
--pure ()
print rs -- force all the Handles to still be alive by this point
File descriptors are process-owned and are copied by forkProcess, so it makes sense to have each clone close their copies.
The case which would be problematic is if a finalizer was cleaning up a system-owned resource, e.g. deleting a file. But I hope no library is relying on finalizers to delete such resources, because as the documentation explains, finalizers are not guaranteed to run. So it's better to use something like bracket to cleanup resources (although the cleanup is still not guaranteed, e.g. if bracket is used from a thread).
What the documentation for forkProcess is warning about is not finalizers, but the fact that other threads will appear to end abruptly inside the forked process. This is especially problematic if those threads are holding locks. Normally, two threads can use modifyMVar_ to ensure that only one thread at a time is running a critical section, and as long as each thread is only holding the lock for a finite amount of time, the other thread can simply wait for the MVar to become available. If you call forkProcess while one thread is in the middle of a modifyMVar_, however, that thread will not continue in the cloned process, and so the cloned process cannot simply call modifyMVar_ or it could get stuck forever while waiting for a non-existing thread to release the lock. Here is a program demonstrating the problem.
import Control.Concurrent
import Control.Monad
import System.Posix.Process
-- >>> main
-- (69216,"forkIO thread",0)
-- (69216,"main thread",1)
-- (69216,"forkIO thread",2)
-- (69216,"main thread",3)
-- (69216,"forkIO thread",4)
-- (69216,"main thread",5)
-- calling forkProcess
-- forkProcess main thread waiting for MVar...
-- (69216,"forkIO thread",6)
-- (69216,"original main thread",7)
-- (69216,"forkIO thread",8)
-- (69216,"original main thread",9)
-- (69216,"forkIO thread",10)
-- (69216,"original main thread",11)
main :: IO ()
main = do
mvar <- newMVar (0 :: Int)
_ <- forkIO $ replicateM_ 6 $ do
modifyMVar_ mvar $ \i -> do
threadDelay 100000
processID <- getProcessID
print (processID, "forkIO thread", i)
pure (i+1)
threadDelay 50000
replicateM_ 3 $ do
modifyMVar_ mvar $ \i -> do
threadDelay 100000
processID <- getProcessID
print (processID, "main thread", i)
pure (i+1)
putStrLn "calling forkProcess"
_ <- forkProcess $ do
threadDelay 25000
replicateM_ 3 $ do
putStrLn "forkProcess main thread waiting for MVar..."
modifyMVar_ mvar $ \i -> do
threadDelay 100000
processID <- getProcessID
print (processID, "forkProcess main thread", i)
pure (i+1)
replicateM_ 3 $ do
modifyMVar_ mvar $ \i -> do
threadDelay 100000
processID <- getProcessID
print (processID, "original main thread", i)
pure (i+1)
threadDelay 100000
As the output shows, the forkProcess main thread gets stuck waiting forever for the MVar, and never prints the forkProcess main thread line. If you move the threadDelays outside the modifyMVar_ critical section, the forkIO thread is a lot less likely to be in the middle of that critical section when forkProcess is called, so you'll see an output which looks like this instead:
(69369,"forkIO thread",0)
(69369,"main thread",1)
(69369,"forkIO thread",2)
(69369,"main thread",3)
(69369,"forkIO thread",4)
(69369,"main thread",5)
calling forkProcess
(69369,"forkIO thread",6)
(69369,"original main thread",7)
forkProcess main thread waiting for MVar...
(69370,"forkProcess main thread",6)
(69369,"forkIO thread",8)
(69369,"original main thread",9)
forkProcess main thread waiting for MVar...
(69370,"forkProcess main thread",7)
(69369,"forkIO thread",10)
(69369,"original main thread",11)
forkProcess main thread waiting for MVar...
(69370,"forkProcess main thread",8)
After the forkProcess call, there are now two MVars which both hold the value 5, and so in the original process, original main thread and forkIO thread are both incrementing one MVar, while in the other process forkProcess main thread is incrementing the other.
I'm using the async library in conjunction with stm in my program.
The main thread forks two threads which run until one of them (it could be either one) encounters a solution. The solution is returned via a TMVar. Neither of them ever waits on any TMVar except to call putTMVar when the solution is found and one of them is guaranteed to run forever unless killed. So how could I possibly be getting "thread blocked indefinitely in an STM transaction" (which seems to happen approximately one in every twenty times) given that at least one of the child threads doesn't execute any blocking STM transactions (or die) until storing a result.
Note the two child threads communicate somewhat with each other using TVars, but not with TMVars.
Simplified code:
main :: IO ()
main = do
output <- newEmptyTMVar
result <- withAsync (child1 output) $ \_ -> withAsync (child2 output) $ \_ ->
let go = do
result <- atomically $ takeTMVar output
if someCondition result
then return result
else go
in go
print result
child1 :: TMVar Result -> IO ()
child1 output = go 0
where
go i = do
case computation1 i of
Nothing -> return ()
Just x -> atomically $ putTMVar x
go (i + 1)
child2 :: TMVar Result -> IO ()
-- Does some other stuff, but also only interacts with its argument to
-- give back a result, same as child1.
I'm new for Haskell. Recently, I was trying to create a game by Haskell. In that game, I use Concurrent to create multiple threads.
data Msg = C Char | Time
forkIO $ userThread chan
forkIO $ processThread startTimer
userThread :: MVar Msg -> IO ()
userThread chan = forever $ do
c <- getChar
putMVar chan (C c)
showStr(show c)
processThread :: MVar Msg -> IO ()
processThread chan = forever $ do
threadDelay (startTimer)
putMVar chan (Time)
I don't know how to define any other data shared between threads. Can I define a variable like C++ (static double xxx) and be visited by any function?
Usually, such variables are created in main, or another IO action.
main = do
chan <- newEmptyMVar
startTimer <- newEmptyMVar
...
forkIO $ userThread chan
forkIO $ processThread startTimer
...
There are some ways to declare "global variables" (mostly IORefs and MVars), but they involve unsafe functions, and are best to be avoided, especially by beginners. Such globals are mostly unnecessary, and it's often better to pass a few arguments around, even if it requires more typing.
In more advanced code, one might use a ReaderT r IO monad to reduce the verbosity of the code which simply passes the MVars around. But at the beginning, passing variables around is fine.
Let's say you have a program with a bunch of threads. The one thread would like to freeze access to stdin, stdout, and stderr (causing any other threads or keyboards to block until its done) so that its output doesn't get interweaved with them. Is there a way to do this directly, or would there have to be a manager thread, you know, managin' the handle. Relatedly, could you cause any input on stdin to block any output on stdout until it received and handled (atomically)?
You can easily simulate a lock for controlling access to a resource with an MVar. You aquire the lock by taking the value with takeMVar and release the lock by replacing the value with putMVar. For example, we can define something like the following
import Control.Concurrent
import Control.Concurrent.MVar
main = do
stdinLock <- newMVar () -- create a new lock for stdin (unaquired)
let
printWithLabel a b = do
takeMVar stdinLock -- aquire the lock for stdin
putStrLn (show a ++ ":")
print b
putMVar stdinLock () -- release the lock for stdin
actions = map fork $ zipWith printWithLabel [1..26] ['A'..]
doneSignals <- sequence actions
sequence doneSignals
return ()
fork :: IO a -> IO (IO ())
fork a = do
done <- newEmptyMVar
forkIO (a >> putMVar done ())
return (takeMVar done)
We could extract the locking functionality into another function
withLock :: MVar () -> IO a -> IO a
withLock lock action = do
takeMVar lock
x <- action
putMVar lock ()
return x
withLock performs an IO action after acquiring a lock and releases it when were done. This doesn't properly handle what to do if the code throws exceptions and notably will not release the lock if an exception is thrown. The Lock in concurrent-extra provides a similar helper function which brackets an operation (handling exceptions) with acquiring and releasing a lock.
In terms of Lock and async the above example can be simplified to
import qualified Control.Concurrent.Lock as Lock
import Control.Concurrent.Async
main = do
stdinLock <- Lock.new
let
printWithLabel a b = Lock.with stdinLock $ do
putStrLn (show a ++ ":")
print b
actions = zipWith printWithLabel [1..26] ['A'..]
doneSignals <- mapM async actions
mapM_ wait doneSignals
If you want a thread reading input on stdin to block output from other threads to stdout you can use a single lock to control both stdin and stdout.
I'm writing something like a music player and get stuck with the playback progress bar.
In my program when the play button is clicked, I use forkIO to fork a thread which controls the progressbar. However, the forked thread now executes a loop. How can I inform that thread to terminate when I stop current song or change songs.
I've been trying to use IORef Var, for example
flag <- newIORef False
forkIO $ progressBarFunc flag
and in the function progreeBarFunc it checks whether flag is true and decides to exit loop or not.
But this does not work.
More generally, how can I tell the forked thread to stop when I use forkIO to fork threads?
In addition, if I have an IORef Var and pass it to the function in forkIO, do the main thread and the forked thread share the same IORef Var or the forked thread actually has a copy of it?
You can communicate between threads using IORefs. The IORef refers to the same thing in the forked thread as it did in the main thread.
There are a few things you should check:
Does the forked thread actually get a chance to test the IORef?
Can the UI interactions you are expecting actually happen from the forked thread? Many UI libraries, including both gtk and OpenGL, have restrictions on which threads can interact with the UI.
Is the flag set for long enough that the forked thread had a chance to see it? If the flag is set to True and then back to False before the forked thread calls readIORef, it won't detect the stop.
One way to address the final problem is to use an Integer instead of a Bool for a flag.
newFlag :: IO (IORef Integer)
newFlag = newIORef 0
An observer of the flag remembers the value of the flag when the observer was created, and stops when it becomes greater. This returns True when the thread can continue (the flag has not been raised).
testFlag :: IORef Integer -> IO (IO Bool)
testFlag flag = do
n <- readIORef flag
return (fmap (<=n) (readIORef flag))
To raise the flag, the signaler increments the value.
raiseFlag :: IORef Integer -> IO ()
raiseFlag ref = atomicModifyIORef ref (\x -> (x+1,()))
This little example program demonstrates an IORef sharing a flag with other threads. It forks new threads when given the input "f", signals the threads to stop when given the input "s", and quits when given the input "q".
main = do
flag <- newFlag
let go = do
command <- getLine
case command of
"f" -> do
continue <- testFlag flag
forkIO $ thread continue
go
"s" -> do
raiseFlag flag
go
"q" -> do
raiseFlag flag
return ()
go
The threads periodically do some "work", which takes half a second, and test for the continue condition before continuing.
thread :: IO Bool -> IO ()
thread continue = go
where
go = do
me <- myThreadId
putStrLn (show me ++ " Outputting")
threadDelay 500000
c <- continue
if c then go else putStrLn (show me ++ " Stopping") >> return ()