To gain access to the concurrency primitives, just import Concurrent in your Haskell module. In GHC, you also need to add the -syslib concurrent option to the command line.
To create a new thread, use forkIO:
forkIO :: IO () -> IO ThreadId
This sparks off a new thread to run the IO computation passed as the first argument.
The returned ThreadId is an abstract type representing a handle to the newly created thread. The ThreadId type is an instance of both Eq and Ord, where the Ord instance implements an arbitrary total ordering over ThreadIds.
Threads may also be killed via the ThreadId:
killThread :: ThreadId -> IO ()
this terminates the given thread (Note: killThread is not implemented in Hugs yet). Any work already done by the thread isn't lost: the computation is suspended until required by another thread. The memory used by the thread will be garbage collected if it isn't referenced from anywhere else.
More generally, an arbitrary exception (see Section 3.7) may be raised in any thread for which we have a ThreadId, with raiseInThread:
raiseInThread :: ThreadId -> Exception -> IO ()
Actually killThread just raises the ThreadKilled exception in the target thread, the normal action of which is to just terminate the thread. The target thread will stop whatever it was doing (even if it was blocked on an MVar or other computation) and handle the exception.
One important property of raiseInThread (and therefore killThread) is that they are synchronous. This means that after performing a raiseInThread operation, the calling thread can be certain that the target thread has received the exception. In other words, the target thread cannot perform any more processing unless it handles the exception that has just been raised in it. This is a useful property to know when dealing with race conditions: eg. if there are two threads that can kill each other, it is guaranteed that only one of the threads will get to kill the other.
The ThreadId for the current thread can be obtained with myThreadId:
myThreadId :: IO ThreadId
NOTE: if you have a ThreadId, you essentially have a pointer to the thread itself. This means the thread itself can't be garbage collected until you drop the ThreadId. This misfeature will hopefully be corrected at a later date.