33

I've got a program which uses several threads. As I understand it, when thread 0 exits, the entire program exits, regardless of any other threads which might still be running.

The thing is, these other threads may have files open. Naturally, this is wrapped in exception-handling code which cleanly closes the files in case of a problem. That also means that if I use killThread (which is implemented via throwTo), the file should also be closed before the thread exits.

My question is, if I just let thread 0 exit, without attempting to stop the other threads, will all the various file handles be closed nicely? Does any buffered output get flushed?

In short, can I just exit, or do I need to manually kill threads first?

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 2
    I prefer not to rely on either `killThread` or `exit` to clean up other threads just as a matter of good programming hygiene. I instrument all my threads to listen for clean-up signals from the main thread so they can all be shut down gracefully. – Gabriella Gonzalez Jun 01 '13 at 15:35
  • Why not try it? IME, most OS will close the files, (ie. leave no handle, fd or other leaks), but outstanding I/O will not be flushed. – Martin James Jun 01 '13 at 19:35
  • 1
    @GabrielGonzalez If you do it that way, it means that a thread will only terminate at certain points. If you forget to check regularly enough, or something takes longer than expected, you can get a situation where you signal the thread to die, and it doesn't (or just takes a really long time). You need to handle exceptions anyway, so why not use `killThread`? – MathematicalOrchid Jun 02 '13 at 09:59
  • @MathematicalOrchid The same is true of a non-threaded program: it will only terminate at certain points. You timeout actions if you want to guarantee graceful and timely recovery. – Gabriella Gonzalez Jun 02 '13 at 15:11

2 Answers2

4

You can use Control.Concurrent.MVar to achieve this. An MVar is essentially a flag which is either ''empty'' or "full". A thread can try to read an MVar and if it is empty it blocks the thread. Wherever you have a thread which performs file IO, create an MVar for it, and pass it that MVar as an argument. Put all the MVars you create into a list:

main = do
  let mvars = sequence (replicate num_of_child_threads newEmptyMVar)
  returnVals <- sequence (zipWith (\m f -> f m) 
                                  mvars 
                                  (list_of_child_threads :: [MVar -> IO a])) 

Once a child thread has finished all file operations that you are worried about, write to the MVar. Instead of writing killThread you can do

mapM_ takeMVar mvars >> killThread 

and where-ever your thread would exit otherwise, just take all the MVars.

See the documentation on GHC concurrency for more details.

user2407038
  • 14,400
  • 3
  • 29
  • 42
  • 3
    I think he's asking a different question: how to stop child threads gracefully if the master thread is done and no longer wants to wait on their results. Side note: you can do what you describe using the `async` package, which also has the added bonus of re-raising exceptions created in child threads if you `wait` on them. – Gabriella Gonzalez Jun 02 '13 at 21:11
  • As I read it, he is concerned that when the parent thread exits, the children will also exit, perhaps causing unwanted side effects if they are writing to file. If the main thread waits for its children to finish all file IO operations, this ceases to be a problem. – user2407038 Jun 04 '13 at 15:31
3

From my testing, I have discovered a few things:

  1. exitFailure and friends only work in thread 0. (The documentation actually says so, if you go to the trouble of reading it. These functions just throw exceptions, which are silently ignored in other threads.)

  2. If an exception kills your thread, or your whole program, any open handles are not flushed. This is excruciatingly annoying when you're desperately trying to figure out exactly where your program crashed!

So it appears it if you want your stuff flushed before the program exits, then you have to implement this. Just letting thread 0 die doesn't flush stuff, doesn't throw any exception, just silently terminates all threads without running exception handlers.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220