HORIZON HASKELLDocslts/ghc-10.0.x8c2f8e82026-10-19Search names, modules, packages, or :: a typeCtrl K

GHC 10.0.0.20260917 · lts/ghc-10.0.x · 8c2f8e8 · 2026-10-19

  • Packagescheduler-2.0.1.0
  • Exports48
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceTypes.hs

Scheduler

3 declarations
datadata Results a
#

Computed results of scheduled jobs.

Constructors

Instances8Functor, Foldable, Traversable, Eq1, Show1, Eq, …
  • Functor ResultsDefined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Foldable ResultsDefined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Traversable ResultsDefined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Eq1 ResultsDefined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Show1 ResultsDefined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Eq a => Eq (Results a)Defined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Read a => Read (Results a)Defined in scheduler-2.0.1.0 · Control.Scheduler.Types
  • Show a => Show (Results a)Defined in scheduler-2.0.1.0 · Control.Scheduler.Types

Regular

valuewithScheduler
  1. :: MonadUnliftIO m
  2. => Comp

    Computation strategy

  3. -> (Scheduler RealWorld a -> m b)

    Action that will be scheduling all the work.

  4. -> m [a]
#

Initialize a scheduler and submit jobs that will be computed sequentially or in parallelel, which is determined by the Computation strategy.

Here are some cool properties about the withScheduler:

  • This function will block until all of the submitted jobs have finished or at least one of them resulted in an exception, which will be re-thrown at the callsite.

  • It is totally fine for nested jobs to submit more jobs for the same or other scheduler

  • It is ok to initialize multiple schedulers at the same time, although that will likely result in suboptimal performance, unless workers are pinned to different capabilities.

  • Warning It is pretty dangerous to schedule jobs that can block, because it might lead to a potential deadlock, if you are not careful. Consider this example. First execution works fine, since there are two scheduled workers, and one can unblock the other, but the second scenario immediately results in a deadlock.

Example3 expressions
withScheduler (ParOn [1,2]) $ \s -> newEmptyMVar >>= (\ mv -> scheduleWork s (readMVar mv) >> scheduleWork s (putMVar mv ()))[(),()]import System.Timeouttimeout 1000000 $ withScheduler (ParOn [1]) $ \s -> newEmptyMVar >>= (\ mv -> scheduleWork s (readMVar mv) >> scheduleWork s (putMVar mv ()))Nothing

Important: In order to get work done truly in parallel, program needs to be compiled with -threaded GHC flag and executed with +RTS -N -RTS to use all available cores.

Stateful workers

valuewithSchedulerWS
  1. :: MonadUnliftIO m
  2. => WorkerStates ws
  3. -> SchedulerWS ws a -> m b
  4. -> m [a]
#

Run a scheduler with stateful workers. Throws MutexException if an attempt is made to concurrently use the same WorkerStates with another SchedulerWS.

Examples

A good example of using stateful workers would be generation of random number in parallel. A lof of times random number generators are not thread safe, so we can work around this problem with a separate stateful generator for each of the workers.

Example10 expressions
import Control.Monad as M ((>=>), replicateM)import Control.Concurrent (yield, threadDelay)import Data.List (sort)-- ^ Above imports are used to make sure output is deterministic, which is needed for doctestimport System.Random.MWC as MWCimport Data.Vector.Unboxed as V (singleton)states <- initWorkerStates (ParN 4) (MWC.initialize . V.singleton . fromIntegral . getWorkerId)let scheduleGen scheduler = scheduleWorkState scheduler (MWC.uniform >=> \r -> yield >> threadDelay 200000 >> pure r)sort <$> withSchedulerWS states (M.replicateM 4 . scheduleGen) :: IO [Double][0.21734983682025255,0.5000843862105709,0.5759825622603018,0.8587171114177893]sort <$> withSchedulerWS states (M.replicateM 4 . scheduleGen) :: IO [Double][2.3598617298033475e-2,9.949679290089553e-2,0.38223134248645885,0.7408640677124702]

In the above example we use four different random number generators from `mwc-random` in order to generate 4 numbers, all in separate threads. The subsequent call to the withSchedulerWS function with the same states is allowed to reuse the same generators, thus avoiding expensive initialization.

Side note - The example presented was crafted with slight trickery in order to guarantee that the output is deterministic, so if you run instructions exactly the same way in GHCI you will get the exact same output. Non-determinism comes from thread scheduling, rather than from random number generator, because we use exactly the same seed for each worker, but workers run concurrently. Exact output is not really needed, except for the doctests to pass.

Trivial (no parallelism)

valuetrivialScheduler_ :: Scheduler s ()
#

The most basic scheduler that simply runs the task instead of scheduling it. Early termination requests are bluntly ignored.

Scheduling computation

8 declarations
valuescheduleWorkId
  1. :: (PrimBase m, s ~ PrimState m)
  2. => Scheduler s a
  3. -> WorkerId -> m a
  4. -> m ()
#

Schedule an action to be picked up and computed by a worker from a pool of jobs. Argument supplied to the job will be the id of the worker doing the job. This is useful for identification of a thread that will be doing the work, since there is one-to-one mapping from ThreadId to WorkerId for a particular scheduler.

Batches

9 declarations
datadata Batch s a
#

Batch is an artifical checkpoint that can be controlled by the user throughout the lifetime of a scheduler.

valuerunBatch
  1. :: (PrimBase m, s ~ PrimState m)
  2. => Scheduler s a
  3. -> Batch s a -> m c
  4. -> m [a]
#

Run a single batch of jobs. Supplied action will not return until all jobs placed on the queue are done or the whole batch is cancelled with one of these cancelBatch, cancelBatch_ or cancelBatchWith.

It waits for all scheduled jobs to finish and collects the computed results into a list. It is a blocking operation, but if there are no jobs in progress it will return immediately. It is safe to continue using the supplied scheduler after this function returns. However, if any of the jobs resulted in an exception it will be rethrown by this function, which, unless caught, will further put the scheduler in a terminated state.

It is important to note that any job that hasn't had its results collected from the scheduler prior to starting the batch it will end up on the batch result list.

valuecancelBatch :: (PrimMonad m, s ~ PrimState m) => Batch s a -> a -> m Bool
#

Cancel batch with supplied identifier, which will lead to scheduler to return FinishedEarly result. This is an idempotent operation and has no affect if currently running batch does not match supplied identifier. Returns False when cancelling did not succeed due to mismatched identifier or does not return at all since all jobs get cancelled immediately. For trivial schedulers however there is no way to perform concurrent cancelation and it will return True.

Early termination

3 declarations
valueterminate :: (PrimMonad m, s ~ PrimState m) => Scheduler s a -> a -> m a
#

As soon as possible try to terminate any computation that is being performed by all workers managed by this scheduler and collect whatever results have been computed, with supplied element guaranteed to being the last one. In case when Results type is returned this function will cause the scheduler to produce FinishedEarly

Important - With Seq strategy this will not stop other scheduled tasks from being computed, although it will make sure their results are discarded.

valueterminateWith :: (PrimMonad m, s ~ PrimState m) => Scheduler s a -> a -> m a
#

Same as terminate, but returning a single element list containing the supplied argument. This can be very useful for parallel search algorithms. In case when Results is the return type this function will cause the scheduler to produce FinishedEarlyWith

Important - Same as with terminate, when Seq strategy is used, this will not prevent computation from continuing, but the scheduler will return only the result supplied to this function.

Workers

5 declarations
newtypenewtype WorkerId
#

A unique id for the worker in the Scheduler context. It will always be a number from 0 up to, but not including, the number of workers a scheduler has, which in turn can always be determined with numWorkers function.

Constructors

Instances9Bounded, Enum, Eq, Integral, Num, Ord, …
  • Bounded WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Enum WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Eq WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Integral WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Num WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Ord WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Read WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Real WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
  • Show WorkerIdDefined in scheduler-2.0.1.0 · Control.Scheduler.Queue
datadata WorkerStates ws
#

Each worker is capable of keeping it's own state, that can be share for different schedulers, but not at the same time. In other words using the same WorkerStates on Control.Scheduler.withSchedulerS concurrently will result in an error. Can be initialized with initWorkerStates

valuenumWorkers :: Scheduler s a -> Int
#

Get the number of workers. Will mainly depend on the computation strategy and/or number of capabilities you have. Related function is getCompWorkers.

Computation strategies

4 declarations
datadata Comp
#

Computation strategy to use when scheduling work.

Constructors

  • Seq

    Sequential computation

  • ParOn ![Int]

    Schedule workers to run on specific capabilities. Specifying an empty list ParOn [] or using Par will result in utilization of all available capabilities.

  • ParN !Word16

    Specify the number of workers that will be handling all the jobs. Difference from ParOn is that workers can jump between cores. Using ParN 0 will result in using all available capabilities.

Instances5Eq, Show, Semigroup, Monoid, NFData
  • Eq CompDefined in scheduler-2.0.1.0 · Control.Scheduler.Computation
  • Show CompDefined in scheduler-2.0.1.0 · Control.Scheduler.Computation
  • Semigroup CompDefined in scheduler-2.0.1.0 · Control.Scheduler.Computation
  • Monoid CompDefined in scheduler-2.0.1.0 · Control.Scheduler.Computation
  • NFData CompDefined in scheduler-2.0.1.0 · Control.Scheduler.Computation
patternpattern Par :: Comp
#

Parallel computation using all available cores. Same as ParOn []

patternpattern Par' :: Comp
#

Parallel computation using all available cores. Same as ParN 0

valuegetCompWorkers :: MonadIO m => Comp -> m Int
#

Figure out how many workers will this computation strategy create.

Note - If at any point during program execution global number of capabilities gets changed with setNumCapabilities, it will have no affect on this function, unless it hasn't yet been called with Par or Par' arguments.

Useful functions

5 declarations
valuetraverse_ :: (Applicative f, Foldable t) => (a -> f ()) -> t a -> f ()
#

This is generally a faster way to traverse while ignoring the result rather than using mapM_.

Exceptions

1 declaration

If any one of the workers dies with an exception, even if that exceptions is asynchronous, it will be re-thrown in the scheduling thread.

Example4 expressions
let didAWorkerDie = handleJust asyncExceptionFromException (return . (== ThreadKilled)) . fmap or:t didAWorkerDiedidAWorkerDie :: Foldable t => IO (t Bool) -> IO BooldidAWorkerDie $ withScheduler Par $ \ s -> scheduleWork s $ pure FalseFalsedidAWorkerDie $ withScheduler Par $ \ s -> scheduleWork s $ myThreadId >>= killThread >> pure FalseTrue