GHC uses preemptive multitasking: context switches can occur at any time. At present, Hugs uses cooperative multitasking: context switches only occur when you use one of the primitives defined in this module. This means that programs such as:
main = forkIO (write 'a') >> write 'b' where write c = putChar c >> write c
will print either aaaaaaaaaaaaaa... or bbbbbbbbbbbb..., instead of some random interleaving of as and bs. In practice, cooperative multitasking is sufficient for writing simple graphical user interfaces.
The yield action forces a context-switch to any other currently runnable threads (if any), and is occasionally useful when implementing concurrency abstractions:
yield :: IO ()
Finally, there are operations to delay a concurrent thread, and to make one wait:
threadDelay :: Int -> IO () -- delay rescheduling for N microseconds threadWaitRead :: Int -> IO () -- wait for input on specified file descriptor threadWaitWrite :: Int -> IO () -- (read and write, respectively).
The threadDelay operation will cause the current thread to suspend for a given number of microseconds. Note that the resolution used by the Haskell runtime system's internal timer together with the fact that the thread may take some time to be rescheduled after the time has expired, means that the accuracy is more like 1/50 second.
threadWaitRead and threadWaitWrite can be used to block a thread until I/O is available on a given file descriptor. These primitives are used by the I/O subsystem to ensure that a thread waiting on I/O doesn't hang the entire system.
Calling a foreign C procedure (such as getchar) that blocks waiting for input will block all threads, in both GHC and Hugs. The GHC I/O system uses non-blocking I/O internally to implement thread-friendly I/O, so calling standard Haskell I/O functions blocks only the thead making the call.