GHC’s runtime system treats the program’s original thread of control differently from other threads. When this thread finishes executing, the runtime system considers the program as a whole to have completed. If any other threads are executing at the time, they are terminated.
As a result, when we have long-running threads that must not be killed, we need to make special arrangements to ensure that the main thread doesn’t complete until the others do. Let’s develop a small library that makes this easy to do:
-- file: ch24/NiceFork.hs import Control.Concurrent import Control.Exception (Exception, try) import qualified Data.Map as M data ThreadStatus = Running | Finished -- terminated normally | Threw Exception -- killed by uncaught exception deriving (Eq, Show) -- | Create a new thread manager. newManager :: IO ThreadManager -- | Create a new managed thread. forkManaged :: ThreadManager -> IO () -> IO ThreadId -- | Immediately return the status of a managed thread. getStatus :: ThreadManager -> ThreadId -> IO (Maybe ThreadStatus) -- | Block until a specific managed thread terminates. waitFor :: ThreadManager -> ThreadId -> IO (Maybe ThreadStatus) -- | Block until all managed threads terminate. waitAll :: ThreadManager -> IO ()
We keep our ThreadManager type abstract
using the usual recipe: we wrap it in a
newtype and prevent clients from creating
values of this type. Among our module’s exports, we list the type
constructor and the IO action ...