From 90eca6686c8224e7012ee8574890f6e875975e72 Mon Sep 17 00:00:00 2001 From: simonmar Date: Fri, 10 May 2002 13:17:29 +0000 Subject: [PATCH] [project @ 2002-05-10 13:17:27 by simonmar] - Add documentation to Control.Concurrent and friends - Other documentation tweaks --- Control/Concurrent.hs | 214 +++++++++++++++++++++++++++++++++--- Control/Concurrent/CVar.hs | 55 --------- Control/Concurrent/Chan.hs | 52 +++++---- Control/Concurrent/MVar.hs | 31 +++++- Control/Concurrent/QSem.hs | 12 +- Control/Concurrent/QSemN.hs | 13 ++- Control/Concurrent/SampleVar.hs | 40 ++++--- Data/Array/Base.hs | 1 + GHC/Base.lhs | 9 +- GHC/Conc.lhs | 126 +++++++++++++++------ GHC/IOBase.lhs | 5 + Text/ParserCombinators/ReadP.hs | 28 ++--- Text/ParserCombinators/ReadPrec.hs | 33 +++--- 13 files changed, 425 insertions(+), 194 deletions(-) delete mode 100644 Control/Concurrent/CVar.hs diff --git a/Control/Concurrent.hs b/Control/Concurrent.hs index 673bac3..5484fc1 100644 --- a/Control/Concurrent.hs +++ b/Control/Concurrent.hs @@ -6,7 +6,7 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency) -- -- A common interface to a collection of useful concurrency -- abstractions. @@ -14,32 +14,61 @@ ----------------------------------------------------------------------------- module Control.Concurrent ( - module Control.Concurrent.Chan, - module Control.Concurrent.CVar, - module Control.Concurrent.MVar, - module Control.Concurrent.QSem, - module Control.Concurrent.QSemN, - module Control.Concurrent.SampleVar, + -- * Concurrent Haskell - forkIO, -- :: IO () -> IO () - yield, -- :: IO () + -- $conc_intro + + -- * Basic concurrency operations -#ifdef __GLASGOW_HASKELL__ ThreadId, + myThreadId, + + forkIO, + killThread, + throwTo, + + -- * Scheduling - -- Forking and suchlike - myThreadId, -- :: IO ThreadId - killThread, -- :: ThreadId -> IO () - throwTo, -- :: ThreadId -> Exception -> IO () + -- $conc_scheduling + yield, -- :: IO () + + -- ** Blocking + + -- $blocking +#ifdef __GLASGOW_HASKELL__ + -- ** Waiting threadDelay, -- :: Int -> IO () threadWaitRead, -- :: Int -> IO () threadWaitWrite, -- :: Int -> IO () #endif - -- merging of streams + -- * Communication abstractions + + module Control.Concurrent.MVar, + module Control.Concurrent.Chan, + module Control.Concurrent.QSem, + module Control.Concurrent.QSemN, + module Control.Concurrent.SampleVar, + + -- * Merging of streams mergeIO, -- :: [a] -> [a] -> IO [a] - nmergeIO -- :: [[a]] -> IO [a] + nmergeIO, -- :: [[a]] -> IO [a] + -- $merge + + -- * GHC\'s implementation of concurrency + + -- |This section describes features specific to GHC\'s + -- implementation of Concurrent Haskell. + + -- ** Terminating the program + + -- $termination + + -- ** Pre-emption + + -- $preemption + ) where import Prelude @@ -60,12 +89,57 @@ import ConcBase #endif import Control.Concurrent.MVar -import Control.Concurrent.CVar import Control.Concurrent.Chan import Control.Concurrent.QSem import Control.Concurrent.QSemN import Control.Concurrent.SampleVar +{- $conc_intro + +The concurrency extension for Haskell is described in the paper +/Concurrent Haskell/ +. + +Concurrency is \"lightweight\", which means that both thread creation +and context switching overheads are extremely low. Scheduling of +Haskell threads is done internally in the Haskell runtime system, and +doesn\'t make use of any operating system-supplied thread packages. + +Haskell threads can communicate via 'MVar's, a kind of synchronised +mutable variable (see "Control.Concurrent.MVar"). Several common +concurrency abstractions can be built from 'MVar's, and these are +provided by the "Concurrent" library. Threads may also communicate +via exceptions. +-} + +{- $conc_scheduling + + Scheduling may be either pre-emptive or co-operative, + depending on the implementation of Concurrent Haskell (see below + for imformation related to specific compilers). In a co-operative + system, 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 @a@s and @b@s. In + practice, cooperative multitasking is sufficient for writing + simple graphical user interfaces. +-} + +{- $blocking +Calling a foreign C procedure (such as @getchar@) that blocks waiting +for input will block /all/ threads, unless the @threadsafe@ attribute +is used on the foreign call (and your compiler \/ operating system +supports it). GHC\'s 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. +-} + -- Thread Ids, specifically the instances of Eq and Ord for these things. -- The ThreadId type itself is defined in std/PrelConc.lhs. @@ -99,6 +173,11 @@ instance Show ThreadId where showString "ThreadId " . showsPrec d (getThreadId (unsafeCoerce# t)) +{- | +This sparks off a new thread to run the 'IO' computation passed as the +first argument, and returns the 'ThreadId' of the newly created +thread. +-} forkIO :: IO () -> IO ThreadId forkIO action = IO $ \ s -> case (fork# action_plus s) of (# s1, id #) -> (# s1, ThreadId id #) @@ -129,6 +208,14 @@ max_buff_size = 1 mergeIO :: [a] -> [a] -> IO [a] nmergeIO :: [[a]] -> IO [a] +-- $merge +-- The 'mergeIO' and 'nmergeIO' functions fork one thread for each +-- input list that concurrently evaluates that list; the results are +-- merged into a single output list. +-- +-- Note: Hugs does not provide these functions, since they require +-- preemptive multitasking. + mergeIO ls rs = newEmptyMVar >>= \ tail_node -> newMVar tail_node >>= \ tail_list -> @@ -186,3 +273,96 @@ nmergeIO lss return val where mapIO f xs = sequence (map f xs) + +-- --------------------------------------------------------------------------- +-- More docs + +{- $termination + + In a standalone GHC program, only the main thread is + required to terminate in order for the process to terminate. + Thus all other forked threads will simply terminate at the same + time as the main thread (the terminology for this kind of + behaviour is \"daemonic threads\"). + + If you want the program to wait for child threads to + finish before exiting, you need to program this yourself. A + simple mechanism is to have each child thread write to an + 'MVar' when it completes, and have the main + thread wait on all the 'MVar's before + exiting: + +> myForkIO :: IO () -> IO (MVar ()) +> myForkIO io = do +> mvar \<- newEmptyMVar +> forkIO (io \`finally\` putMVar mvar ()) +> return mvar + + Note that we use 'finally' from the + "Exception" module to make sure that the + 'MVar' is written to even if the thread dies or + is killed for some reason. + + A better method is to keep a global list of all child + threads which we should wait for at the end of the program: + +> children :: MVar [MVar ()] +> children = unsafePerformIO (newMVar []) +> +> waitForChildren :: IO () +> waitForChildren = do +> (mvar:mvars) \<- takeMVar children +> putMVar children mvars +> takeMVar mvar +> waitForChildren +> +> forkChild :: IO () -> IO () +> forkChild io = do +> mvar \<- newEmptyMVar +> forkIO (p \`finally\` putMVar mvar ()) +> childs \<- takeMVar children +> putMVar children (mvar:childs) +> +> later = flip finally +> +> main = +> later waitForChildren $ +> ... + + The main thread principle also applies to calls to Haskell from + outside, using @foreign export@. When the @foreign export@ed + function is invoked, it starts a new main thread, and it returns + when this main thread terminates. If the call causes new + threads to be forked, they may remain in the system after the + @foreign export@ed function has returned. +-} + +{- $preemption + + GHC implements pre-emptive multitasking: the execution of + threads are interleaved in a random fashion. More specifically, + a thread may be pre-empted whenever it allocates some memory, + which unfortunately means that tight loops which do no + allocation tend to lock out other threads (this only seems to + happen with pathalogical benchmark-style code, however). + + The rescheduling timer runs on a 20ms granularity by + default, but this may be altered using the + @-i@ RTS option. After a rescheduling + \"tick\" the running thread is pre-empted as soon as + possible. + + One final note: the + @aaaa@ @bbbb@ example may not + work too well on GHC (see Scheduling, above), due + to the locking on a 'Handle'. Only one thread + may hold the lock on a 'Handle' at any one + time, so if a reschedule happens while a thread is holding the + lock, the other thread won\'t be able to run. The upshot is that + the switch from @aaaa@ to + @bbbbb@ happens infrequently. It can be + improved by lowering the reschedule tick period. We also have a + patch that causes a reschedule whenever a thread waiting on a + lock is woken up, but haven\'t found it to be useful for anything + other than this example :-) +-} diff --git a/Control/Concurrent/CVar.hs b/Control/Concurrent/CVar.hs deleted file mode 100644 index 0d7c7b8..0000000 --- a/Control/Concurrent/CVar.hs +++ /dev/null @@ -1,55 +0,0 @@ ------------------------------------------------------------------------------ --- | --- Module : Control.Concurrent.CVar --- Copyright : (c) The University of Glasgow 2001 --- License : BSD-style (see the file libraries/base/LICENSE) --- --- Maintainer : libraries@haskell.org --- Stability : experimental --- Portability : non-portable --- --- Channel variables are one-element channels. --- ------------------------------------------------------------------------------ - -module Control.Concurrent.CVar - ( -- abstract - CVar - , newCVar -- :: IO (CVar a) - , writeCVar -- :: CVar a -> a -> IO () - , readCVar -- :: CVar a -> IO a - ) where - -import Prelude - -import Control.Concurrent.MVar - --- @MVars@ provide the basic mechanisms for synchronising access to a --- shared resource. @CVars@, or channel variables, provide an abstraction --- that guarantee that the producer is not allowed to run riot, but --- enforces the interleaved access to the channel variable,i.e., a --- producer is forced to wait up for a consumer to remove the previous --- value before it can deposit a new one in the @CVar@. - -data CVar a - = CVar (MVar a) -- prod -> cons - (MVar ()) -- cons -> prod - -newCVar :: IO (CVar a) -newCVar - = newEmptyMVar >>= \ datum -> - newMVar () >>= \ ack -> - return (CVar datum ack) - -writeCVar :: CVar a -> a -> IO () - -writeCVar (CVar datum ack) val - = takeMVar ack >> - putMVar datum val >> - return () - -readCVar :: CVar a -> IO a -readCVar (CVar datum ack) - = takeMVar datum >>= \ val -> - putMVar ack () >> - return val diff --git a/Control/Concurrent/Chan.hs b/Control/Concurrent/Chan.hs index e756d1a..09afdb5 100644 --- a/Control/Concurrent/Chan.hs +++ b/Control/Concurrent/Chan.hs @@ -6,31 +6,29 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency). -- --- Standard, unbounded channel abstraction. +-- Unbounded channels. -- ----------------------------------------------------------------------------- module Control.Concurrent.Chan - ( Chan -- abstract - - -- creator - , newChan -- :: IO (Chan a) - - -- operators - , writeChan -- :: Chan a -> a -> IO () - , readChan -- :: Chan a -> IO a - , dupChan -- :: Chan a -> IO (Chan a) - , unGetChan -- :: Chan a -> a -> IO () - - , isEmptyChan -- :: Chan a -> IO Bool - - -- stream interface - , getChanContents -- :: Chan a -> IO [a] - , writeList2Chan -- :: Chan a -> [a] -> IO () - - ) where + ( + -- * The 'Chan' type + Chan, -- abstract + + -- * Operations + newChan, -- :: IO (Chan a) + writeChan, -- :: Chan a -> a -> IO () + readChan, -- :: Chan a -> IO a + dupChan, -- :: Chan a -> IO (Chan a) + unGetChan, -- :: Chan a -> a -> IO () + isEmptyChan, -- :: Chan a -> IO Bool + + -- * Stream interface + getChanContents, -- :: Chan a -> IO [a] + writeList2Chan, -- :: Chan a -> [a] -> IO () + ) where import Prelude @@ -41,6 +39,7 @@ import Control.Concurrent.MVar -- of the channel contents,i.e., the read- and write ends. Empty @MVar@s -- are used to handle consumers trying to read from an empty channel. +-- |'Chan' is an abstract type representing an unbounded FIFO channel. data Chan a = Chan (MVar (Stream a)) (MVar (Stream a)) @@ -55,6 +54,7 @@ data ChItem a = ChItem a (Stream a) -- @newChan@ sets up the read and write end of a channel by initialising -- these two @MVar@s with an empty @MVar@. +-- |Build and returns a new instance of 'Chan'. newChan :: IO (Chan a) newChan = do hole <- newEmptyMVar @@ -67,6 +67,7 @@ newChan = do -- filled in with a new stream element holding the entered value and the -- new hole. +-- |Write a value to a 'Chan'. writeChan :: Chan a -> a -> IO () writeChan (Chan _read write) val = do new_hole <- newEmptyMVar @@ -74,6 +75,7 @@ writeChan (Chan _read write) val = do putMVar old_hole (ChItem val new_hole) return new_hole +-- |Read the next value from the 'Chan'. readChan :: Chan a -> IO a readChan (Chan read _write) = do modifyMVar read $ \read_end -> do @@ -82,12 +84,17 @@ readChan (Chan read _write) = do -- else dupChan doesn't work return (new_read_end, val) +-- |Duplicate a 'Chan': the duplicate channel begins empty, but data written to +-- either channel from then on will be available from both. Hence this creates +-- a kind of broadcast channel, where data written by anyone is seen by +-- everyone else. dupChan :: Chan a -> IO (Chan a) dupChan (Chan _read write) = do hole <- readMVar write new_read <- newMVar hole return (Chan new_read write) +-- |Put a data item back onto a channel, where it will be the next item read. unGetChan :: Chan a -> a -> IO () unGetChan (Chan read _write) val = do new_read_end <- newEmptyMVar @@ -95,6 +102,7 @@ unGetChan (Chan read _write) val = do putMVar new_read_end (ChItem val read_end) return new_read_end +-- |Returns 'True' if the supplied 'Chan' is empty. isEmptyChan :: Chan a -> IO Bool isEmptyChan (Chan read write) = do withMVar read $ \r -> do @@ -104,6 +112,8 @@ isEmptyChan (Chan read write) = do -- Operators for interfacing with functional streams. +-- |Return a lazy list representing the contents of the supplied +-- 'Chan', much like 'IO.hGetContents'. getChanContents :: Chan a -> IO [a] getChanContents ch = unsafeInterleaveIO (do @@ -112,6 +122,6 @@ getChanContents ch return (x:xs) ) -------------- +-- |Write an entire list of items to a 'Chan'. writeList2Chan :: Chan a -> [a] -> IO () writeList2Chan ch ls = sequence_ (map (writeChan ch) ls) diff --git a/Control/Concurrent/MVar.hs b/Control/Concurrent/MVar.hs index eb1f03f..c56750f 100644 --- a/Control/Concurrent/MVar.hs +++ b/Control/Concurrent/MVar.hs @@ -6,14 +6,16 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency) -- --- MVars: Synchronising variables +-- Synchronising variables -- ----------------------------------------------------------------------------- module Control.Concurrent.MVar - ( MVar -- abstract + ( + -- * @MVar@s + MVar -- abstract , newEmptyMVar -- :: IO (MVar a) , newMVar -- :: a -> IO (MVar a) , takeMVar -- :: MVar a -> IO a @@ -52,6 +54,10 @@ throw = throwIO #endif #ifdef __GLASGOW_HASKELL__ +{-| + This is a combination of 'takeMVar' and 'putMVar'; ie. it takes the value + from the 'MVar', puts it back, and also returns it. +-} readMVar :: MVar a -> IO a readMVar m = block $ do @@ -59,11 +65,17 @@ readMVar m = putMVar m a return a +-- |Swap the contents of an 'MVar' for a new value. swapMVar :: MVar a -> a -> IO a swapMVar mvar new = modifyMVar mvar (\old -> return (new,old)) #endif --- put back the same value, return something +{-| + 'withMVar' is a safe wrapper for operating on the contents of an + 'MVar'. This operation is exception-safe: it will replace the + original contents of the 'MVar' if an exception is raised (see + "Control.Exception"). +-} withMVar :: MVar a -> (a -> IO b) -> IO b withMVar m io = block $ do @@ -73,7 +85,11 @@ withMVar m io = putMVar m a return b --- put back a new value, return () +{-| + A safe wrapper for modifying the contents of an 'MVar'. Like 'withMVar', + 'modifyMVar' will replace the original contents of the 'MVar' if an + exception is raised during the operation. +-} modifyMVar_ :: MVar a -> (a -> IO a) -> IO () modifyMVar_ m io = block $ do @@ -82,7 +98,10 @@ modifyMVar_ m io = (\e -> do putMVar m a; throw e) putMVar m a' --- put back a new value, return something +{-| + A slight variation on 'modifyMVar_' that allows a value to be + returned (@b@) in addition to the modified value of the 'MVar'. +-} modifyMVar :: MVar a -> (a -> IO (a,b)) -> IO b modifyMVar m io = block $ do diff --git a/Control/Concurrent/QSem.hs b/Control/Concurrent/QSem.hs index 04064e4..2ef6dff 100644 --- a/Control/Concurrent/QSem.hs +++ b/Control/Concurrent/QSem.hs @@ -6,14 +6,15 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency) -- --- General semaphores +-- Simple quantity semaphores. -- ----------------------------------------------------------------------------- module Control.Concurrent.QSem - ( QSem, -- abstract + ( -- * Simple Quantity Semaphores + QSem, -- abstract newQSem, -- :: Int -> IO QSem waitQSem, -- :: QSem -> IO () signalQSem -- :: QSem -> IO () @@ -29,13 +30,17 @@ import Control.Concurrent.MVar -- representing threads currently waiting. The counter is a shared -- variable, ensuring the mutual exclusion on its access. +-- |A 'QSem' is a simple quantity semaphore, in which the available +-- \"quantity\" is always dealt with in units of one. newtype QSem = QSem (MVar (Int, [MVar ()])) +-- |Build a new 'QSem' newQSem :: Int -> IO QSem newQSem init = do sem <- newMVar (init,[]) return (QSem sem) +-- |Wait for a unit to become available waitQSem :: QSem -> IO () waitQSem (QSem sem) = do (avail,blocked) <- takeMVar sem -- gain ex. access @@ -55,6 +60,7 @@ waitQSem (QSem sem) = do putMVar sem (0, blocked++[block]) takeMVar block +-- |Signal that a unit of the 'QSem' is available signalQSem :: QSem -> IO () signalQSem (QSem sem) = do (avail,blocked) <- takeMVar sem diff --git a/Control/Concurrent/QSemN.hs b/Control/Concurrent/QSemN.hs index 9ba46a2..8e95903 100644 --- a/Control/Concurrent/QSemN.hs +++ b/Control/Concurrent/QSemN.hs @@ -6,14 +6,16 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency) -- --- Quantity semaphores +-- Quantity semaphores in which each thread may wait for an arbitrary +-- \"amount\". -- ----------------------------------------------------------------------------- module Control.Concurrent.QSemN - ( QSemN, -- abstract + ( -- * General Quantity Semaphores + QSemN, -- abstract newQSemN, -- :: Int -> IO QSemN waitQSemN, -- :: QSemN -> Int -> IO () signalQSemN -- :: QSemN -> Int -> IO () @@ -23,13 +25,17 @@ import Prelude import Control.Concurrent.MVar +-- |A 'QSemN' is a quantity semaphore, in which the available +-- \"quantity\" may be signalled or waited for in arbitrary amounts. newtype QSemN = QSemN (MVar (Int,[(Int,MVar ())])) +-- |Build a new 'QSemN' with a supplied initial quantity. newQSemN :: Int -> IO QSemN newQSemN init = do sem <- newMVar (init,[]) return (QSemN sem) +-- |Wait for the specified quantity to become available waitQSemN :: QSemN -> Int -> IO () waitQSemN (QSemN sem) sz = do (avail,blocked) <- takeMVar sem -- gain ex. access @@ -42,6 +48,7 @@ waitQSemN (QSemN sem) sz = do putMVar sem (avail, blocked++[(sz,block)]) takeMVar block +-- |Signal that a given quantity is now available from the 'QSemN'. signalQSemN :: QSemN -> Int -> IO () signalQSemN (QSemN sem) n = do (avail,blocked) <- takeMVar sem diff --git a/Control/Concurrent/SampleVar.hs b/Control/Concurrent/SampleVar.hs index 2b29092..ccb93e1 100644 --- a/Control/Concurrent/SampleVar.hs +++ b/Control/Concurrent/SampleVar.hs @@ -6,7 +6,7 @@ -- -- Maintainer : libraries@haskell.org -- Stability : experimental --- Portability : non-portable +-- Portability : non-portable (concurrency) -- -- Sample variables -- @@ -14,6 +14,7 @@ module Control.Concurrent.SampleVar ( + -- * Sample Variables SampleVar, -- :: type _ = newEmptySampleVar, -- :: IO (SampleVar a) @@ -28,20 +29,21 @@ import Prelude import Control.Concurrent.MVar --- Sample variables are slightly different from a normal MVar: +-- | +-- Sample variables are slightly different from a normal 'MVar': -- --- * Reading an empty SampleVar causes the reader to block. --- (same as takeMVar on empty MVar) +-- * Reading an empty 'SampleVar' causes the reader to block. +-- (same as 'takeMVar' on empty 'MVar') -- --- * Reading a filled SampleVar empties it and returns value. --- (same as takeMVar) +-- * Reading a filled 'SampleVar' empties it and returns value. +-- (same as 'takeMVar') -- --- * Writing to an empty SampleVar fills it with a value, and --- potentially, wakes up a blocked reader (same as for putMVar on --- empty MVar). +-- * Writing to an empty 'SampleVar' fills it with a value, and +-- potentially, wakes up a blocked reader (same as for 'putMVar' on +-- empty 'MVar'). -- --- * Writing to a filled SampleVar overwrites the current value. --- (different from putMVar on full MVar.) +-- * Writing to a filled 'SampleVar' overwrites the current value. +-- (different from 'putMVar' on full 'MVar'.) type SampleVar a = MVar (Int, -- 1 == full @@ -49,19 +51,20 @@ type SampleVar a -- <0 no of readers blocked MVar a) --- Initally, a SampleVar is empty/unfilled. - +-- |Build a new, empty, 'SampleVar' newEmptySampleVar :: IO (SampleVar a) newEmptySampleVar = do v <- newEmptyMVar newMVar (0,v) +-- |Build a 'SampleVar' with an initial value. newSampleVar :: a -> IO (SampleVar a) newSampleVar a = do v <- newEmptyMVar putMVar v a newMVar (1,v) +-- |If the SampleVar is full, leave it empty. Otherwise, do nothing. emptySampleVar :: SampleVar a -> IO () emptySampleVar v = do (readers, var) <- takeMVar v @@ -70,22 +73,25 @@ emptySampleVar v = do else putMVar v (readers,var) +-- |Wait for a value to become available, then take it and return. +readSampleVar :: SampleVar a -> IO a +readSampleVar svar = do -- -- filled => make empty and grab sample -- not filled => try to grab value, empty when read val. -- -readSampleVar :: SampleVar a -> IO a -readSampleVar svar = do (readers,val) <- takeMVar svar putMVar svar (readers-1,val) takeMVar val +-- |Write a value into the 'SampleVar', overwriting any previous value that +-- was there. +writeSampleVar :: SampleVar a -> a -> IO () +writeSampleVar svar v = do -- -- filled => overwrite -- not filled => fill, write val -- -writeSampleVar :: SampleVar a -> a -> IO () -writeSampleVar svar v = do (readers,val) <- takeMVar svar case readers of 1 -> diff --git a/Data/Array/Base.hs b/Data/Array/Base.hs index dee798f..b2a54bf 100644 --- a/Data/Array/Base.hs +++ b/Data/Array/Base.hs @@ -14,6 +14,7 @@ -- ----------------------------------------------------------------------------- +-- #hide module Data.Array.Base where import Prelude diff --git a/GHC/Base.lhs b/GHC/Base.lhs index b19e83a..847c1fe 100644 --- a/GHC/Base.lhs +++ b/GHC/Base.lhs @@ -777,10 +777,7 @@ unpacking the strings of error messages. \begin{code} unpackCString# :: Addr# -> [Char] {-# NOINLINE [1] unpackCString# #-} -unpackCString# a = unpackCStringList# a - -unpackCStringList# :: Addr# -> [Char] -unpackCStringList# addr +unpackCString# addr = unpack 0# where unpack nh @@ -849,10 +846,10 @@ unpackNBytes# addr len# = unpack [] (len# -# 1#) {-# RULES "unpack" [~1] forall a . unpackCString# a = build (unpackFoldrCString# a) -"unpack-list" [1] forall a . unpackFoldrCString# a (:) [] = unpackCStringList# a +"unpack-list" [1] forall a . unpackFoldrCString# a (:) [] = unpackCString# a "unpack-append" forall a n . unpackFoldrCString# a (:) n = unpackAppendCString# a n --- There's a built-in rule (in GHC.Rules.lhs) for +-- There's a built-in rule (in PrelRules.lhs) for -- unpackFoldr "foo" c (unpackFoldr "baz" c n) = unpackFoldr "foobaz" c n #-} diff --git a/GHC/Conc.lhs b/GHC/Conc.lhs index 4c4f322..54bdb25 100644 --- a/GHC/Conc.lhs +++ b/GHC/Conc.lhs @@ -68,21 +68,58 @@ data ThreadId = ThreadId ThreadId# -- ToDo: data ThreadId = ThreadId (Weak ThreadId#) -- But since ThreadId# is unlifted, the Weak type must use open -- type variables. +{- ^ +A 'ThreadId' is an abstract type representing a handle to a thread. +'ThreadId' is an instance of 'Eq', 'Ord' and 'Show', where +the 'Ord' instance implements an arbitrary total ordering over +'ThreadId's. The 'Show' instance lets you convert an arbitrary-valued +'ThreadId' to string form; showing a 'ThreadId' value is occasionally +useful when debugging or diagnosing the behaviour of a concurrent +program. + +NOTE: in GHC, 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. +-} --forkIO has now been hoisted out into the Concurrent library. +{- | 'killThread' terminates the given thread (Note: 'killThread' is +not implemented in Hugs). 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. The 'killThread' function may be defined in +terms of 'throwTo': + +> killThread = throwTo (AsyncException ThreadKilled) +-} killThread :: ThreadId -> IO () killThread (ThreadId id) = IO $ \ s -> case (killThread# id (AsyncException ThreadKilled) s) of s1 -> (# s1, () #) +{- | 'throwTo' raises an arbitrary exception in the target thread. + +'throwTo' does not return until the exception has been raised in the +target thread. The calling thread can thus be certain that the target +thread has received the exception. 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. -} throwTo :: ThreadId -> Exception -> IO () throwTo (ThreadId id) ex = IO $ \ s -> case (killThread# id ex s) of s1 -> (# s1, () #) +-- | Returns the 'ThreadId' of the calling thread. myThreadId :: IO ThreadId myThreadId = IO $ \s -> case (myThreadId# s) of (# s1, id #) -> (# s1, ThreadId id #) + +-- |The 'yield' action allows (forces, in a co-operative multitasking +-- implementation) a context-switch to any other currently runnable +-- threads (if any), and is occasionally useful when implementing +-- concurrency abstractions. yield :: IO () yield = IO $ \s -> case (yield# s) of s1 -> (# s1, () #) @@ -136,58 +173,70 @@ writes. \begin{code} --Defined in IOBase to avoid cycle: data MVar a = MVar (SynchVar# RealWorld a) +-- |Create an 'MVar' which is initially empty. newEmptyMVar :: IO (MVar a) newEmptyMVar = IO $ \ s# -> case newMVar# s# of (# s2#, svar# #) -> (# s2#, MVar svar# #) +-- |Create an 'MVar' which contains the supplied value. +newMVar :: a -> IO (MVar a) +newMVar value = + newEmptyMVar >>= \ mvar -> + putMVar mvar value >> + return mvar + +-- |Return the contents of the 'MVar'. If the 'MVar' is currently +-- empty, 'takeMVar' will wait until it is full. After a 'takeMVar', +-- the 'MVar' is left empty. +-- +-- If several threads are competing to take the same 'MVar', one is chosen +-- to continue at random when the 'MVar' becomes full. takeMVar :: MVar a -> IO a takeMVar (MVar mvar#) = IO $ \ s# -> takeMVar# mvar# s# +-- |Put a value into an 'MVar'. If the 'MVar' is currently full, +-- 'putMVar' will wait until it becomes empty. +-- +-- If several threads are competing to fill the same 'MVar', one is +-- chosen to continue at random with the 'MVar' becomes empty. putMVar :: MVar a -> a -> IO () putMVar (MVar mvar#) x = IO $ \ s# -> case putMVar# mvar# x s# of s2# -> (# s2#, () #) -tryPutMVar :: MVar a -> a -> IO Bool -tryPutMVar (MVar mvar#) x = IO $ \ s# -> - case tryPutMVar# mvar# x s# of - (# s, 0# #) -> (# s, False #) - (# s, _ #) -> (# s, True #) - -newMVar :: a -> IO (MVar a) -newMVar value = - newEmptyMVar >>= \ mvar -> - putMVar mvar value >> - return mvar - --- tryTakeMVar is a non-blocking takeMVar +-- |A non-blocking version of 'takeMVar'. The 'tryTakeMVar' function +-- returns immediately, with 'Nothing' if the 'MVar' was empty, or +-- @'Just' a@ if the 'MVar' was full with contents @a@. After 'tryTakeMVar', +-- the 'MVar' is left empty. tryTakeMVar :: MVar a -> IO (Maybe a) tryTakeMVar (MVar m) = IO $ \ s -> case tryTakeMVar# m s of (# s, 0#, _ #) -> (# s, Nothing #) -- MVar is empty (# s, _, a #) -> (# s, Just a #) -- MVar is full -{- - Low-level op. for checking whether an MVar is filled-in or not. - Notice that the boolean value returned is just a snapshot of - the state of the MVar. By the time you get to react on its result, - the MVar may have been filled (or emptied) - so be extremely - careful when using this operation. - - Use tryTakeMVar instead if possible. +-- |A non-blocking version of 'putMVar'. The 'tryPutMVar' function +-- attempts to put the value @a@ into the 'MVar', returning 'True' if +-- it was successful, or 'False' otherwise. +tryPutMVar :: MVar a -> a -> IO Bool +tryPutMVar (MVar mvar#) x = IO $ \ s# -> + case tryPutMVar# mvar# x s# of + (# s, 0# #) -> (# s, False #) + (# s, _ #) -> (# s, True #) - If you can re-work your abstractions to avoid having to - depend on isEmptyMVar, then you're encouraged to do so, - i.e., consider yourself warned about the imprecision in - general of isEmptyMVar :-) --} +-- |Check whether a given 'MVar' is empty. +-- +-- Notice that the boolean value returned is just a snapshot of +-- the state of the MVar. By the time you get to react on its result, +-- the MVar may have been filled (or emptied) - so be extremely +-- careful when using this operation. Use 'tryTakeMVar' instead if possible. isEmptyMVar :: MVar a -> IO Bool isEmptyMVar (MVar mv#) = IO $ \ s# -> case isEmptyMVar# mv# s# of (# s2#, flg #) -> (# s2#, not (flg ==# 0#) #) --- Like addForeignPtrFinalizer, but for MVars +-- |Add a finalizer to an 'MVar'. See "Foreign.ForeignPtr" and +-- "System.Mem.Weak" for more about finalizers. addMVarFinalizer :: MVar a -> IO () -> IO () addMVarFinalizer (MVar m) finalizer = IO $ \s -> case mkWeak# m () finalizer s of { (# s1, w #) -> (# s1, () #) } @@ -200,20 +249,25 @@ addMVarFinalizer (MVar m) finalizer = %* * %************************************************************************ -@threadDelay@ delays rescheduling of a thread until the indicated -number of microseconds have elapsed. Generally, the microseconds are -counted by the context switch timer, which ticks in virtual time; -however, when there are no runnable threads, we don't accumulate any -virtual time, so we start ticking in real time. (The granularity is -the effective resolution of the context switch timer, so it is -affected by the RTS -C option.) - @threadWaitRead@ delays rescheduling of a thread until input on the specified file descriptor is available for reading (just like select). @threadWaitWrite@ is similar, but for writing on a file descriptor. \begin{code} -threadDelay, threadWaitRead, threadWaitWrite :: Int -> IO () +-- |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. +threadDelay :: Int -> IO () + +-- | Block the current thread until data is available to read on the +-- given file descriptor. +threadWaitRead :: Int -> IO () + +-- | Block the current thread until data can be written to the +-- given file descriptor. +threadWaitWrite :: Int -> IO () threadDelay (I# ms) = IO $ \s -> case delay# ms s of s -> (# s, () #) threadWaitRead (I# fd) = IO $ \s -> case waitRead# fd s of s -> (# s, () #) diff --git a/GHC/IOBase.lhs b/GHC/IOBase.lhs index 3e6462a..b2ec6e6 100644 --- a/GHC/IOBase.lhs +++ b/GHC/IOBase.lhs @@ -123,6 +123,11 @@ unsafeInterleaveIO (IO m) -- Handle type data MVar a = MVar (MVar# RealWorld a) +{- ^ +An 'MVar' (pronounced \"em-var\") is a synchronising variable, used +for communication between concurrent threads. It can be thought of +as a a box, which may be empty or full. +-} -- pull in Eq (Mvar a) too, to avoid GHC.Conc being an orphan-instance module instance Eq (MVar a) where diff --git a/Text/ParserCombinators/ReadP.hs b/Text/ParserCombinators/ReadP.hs index d926ef1..1e01ae9 100644 --- a/Text/ParserCombinators/ReadP.hs +++ b/Text/ParserCombinators/ReadP.hs @@ -14,26 +14,26 @@ module Text.ParserCombinators.ReadP ( -- * The 'ReadP' type - ReadP -- :: * -> *; instance Functor, Monad, MonadPlus + ReadP, -- :: * -> *; instance Functor, Monad, MonadPlus -- * Primitive operations - , get -- :: ReadP Char - , look -- :: ReadP String - , (+++) -- :: ReadP a -> ReadP a -> ReadP a + get, -- :: ReadP Char + look, -- :: ReadP String + (+++), -- :: ReadP a -> ReadP a -> ReadP a -- * Other operations - , pfail -- :: ReadP a - , satisfy -- :: (Char -> Bool) -> ReadP Char - , char -- :: Char -> ReadP Char - , string -- :: String -> ReadP String - , munch -- :: (Char -> Bool) -> ReadP String - , munch1 -- :: (Char -> Bool) -> ReadP String - , skipSpaces -- :: ReadP () - , choice -- :: [ReadP a] -> ReadP a + pfail, -- :: ReadP a + satisfy, -- :: (Char -> Bool) -> ReadP Char + char, -- :: Char -> ReadP Char + string, -- :: String -> ReadP String + munch, -- :: (Char -> Bool) -> ReadP String + munch1, -- :: (Char -> Bool) -> ReadP String + skipSpaces, -- :: ReadP () + choice, -- :: [ReadP a] -> ReadP a -- * Conversions - , readP_to_S -- :: ReadP a -> ReadS a - , readS_to_P -- :: ReadS a -> ReadP a + readP_to_S, -- :: ReadP a -> ReadS a + readS_to_P, -- :: ReadS a -> ReadP a ) where diff --git a/Text/ParserCombinators/ReadPrec.hs b/Text/ParserCombinators/ReadPrec.hs index bfb4fc2..b501a8e 100644 --- a/Text/ParserCombinators/ReadPrec.hs +++ b/Text/ParserCombinators/ReadPrec.hs @@ -12,30 +12,31 @@ ----------------------------------------------------------------------------- module Text.ParserCombinators.ReadPrec - ( ReadPrec -- :: * -> *; instance Functor, Monad, MonadPlus + ( + ReadPrec, -- :: * -> *; instance Functor, Monad, MonadPlus -- * Precedences - , Prec -- :: *; = Int - , minPrec -- :: Prec; = 0 + Prec, -- :: *; = Int + minPrec, -- :: Prec; = 0 -- * Primitive operations - , lift -- :: ReadP a -> ReadPrec a - , prec -- :: Prec -> ReadPrec a -> ReadPrec a - , step -- :: ReadPrec a -> ReadPrec a - , reset -- :: ReadPrec a -> ReadPrec a + lift, -- :: ReadP a -> ReadPrec a + prec, -- :: Prec -> ReadPrec a -> ReadPrec a + step, -- :: ReadPrec a -> ReadPrec a + reset, -- :: ReadPrec a -> ReadPrec a -- * Other operations - , get -- :: ReadPrec Char - , look -- :: ReadPrec String - , (+++) -- :: ReadPrec a -> ReadPrec a -> ReadPrec a - , pfail -- :: ReadPrec a - , choice -- :: [ReadPrec a] -> ReadPrec a + get, -- :: ReadPrec Char + look, -- :: ReadPrec String + (+++), -- :: ReadPrec a -> ReadPrec a -> ReadPrec a + pfail, -- :: ReadPrec a + choice, -- :: [ReadPrec a] -> ReadPrec a -- converters - , readPrec_to_P -- :: ReadPrec a -> (Int -> ReadP a) - , readP_to_Prec -- :: (Int -> ReadP a) -> ReadPrec a - , readPrec_to_S -- :: ReadPrec a -> (Int -> ReadS a) - , readS_to_Prec -- :: (Int -> ReadS a) -> ReadPrec a + readPrec_to_P, -- :: ReadPrec a -> (Int -> ReadP a) + readP_to_Prec, -- :: (Int -> ReadP a) -> ReadPrec a + readPrec_to_S, -- :: ReadPrec a -> (Int -> ReadS a) + readS_to_Prec, -- :: (Int -> ReadS a) -> ReadPrec a ) where -- 1.7.10.4