Talk:Parallelism vs. Concurrency

From HaskellWiki
Revision as of 10:16, 6 October 2021 by Atravers (talk | contribs) (Terminology changed)
Jump to navigation Jump to search

Parallelism vs concurrency: what's the difference?

Visible side effects:

  • Have a look at this ugly eysore "prototype definition" of par:
par     :: a -> b -> b
par x y =  case
             unsafeLocalState (forkIO (evaluate x >> return ()))
           of
             !_ -> y

where:

      evaluate :: a -> IO a
      forkIO   :: IO () -> IO ThreadId

Assuming:

  • x is well-defined (it contains no unsafe... calls),
  • x is well-behaved (not throwing exceptions or causing errors);

then:

  1. forkIO attaches a ThreadId to its argument, adds it to the work-queue and returns the identifier;
  2. par then returns y;
  3. Some time later, forkIO's argument is called, causing evaluate to start evaluating x.

If y is still being evaluated when the evaluation of x commences, then we have concurrency, but with no visible side-effects - it behaves like elementary parallelism.

  • Now have a look at this nearly-as-ugly prototype definition for spawnIO forkIO:
forkIO     :: IO () -> IO ThreadId
forkIO act =  do t <- unsafeInterleaveIO act
                 case attachThreadId t of
                   Nothing -> itsThreadId t
                   Just i  -> par t (return i)

where:

      attachThreadId :: a -> IO (Maybe ThreadId)
      itsThreadId :: a -> IO ThreadId

Assuming:

  • par, attachThreadId and itsThreadId are primitive,
  • attachThreadId would return Nothing if it's argument already has been assigned a ThreadId;

then:

  1. A new ThreadId is assigned to the new (and suspended) value t;
  2. evaluating par t i causes t to be added to the work-queue by the implementation;
  3. the ThreadId for t is then returned;
  4. Some time later, the implementation discovers that a ThreadId has been attached to t and uses the ThreadId to immediately start a new thread for evaluating t;

This is parallelism, but having visible side effects - it behaves like elementary concurrency.

Can either prototype definition potentially go mainstream?

  • As shown by it's type signature, par is meant to have no visible effects: avoiding the use of unsafeLocalState means making it primitive;
  • While the use of unsafeInterleaveIO may annoy some, it being one of the earlier Haskell extensions means it's widely available.

For now, using a primitive par (and others) to define forkIO looks like the simplest option...but if using unsafeInterleaveIO really does annoy you, how about this:

forkIO       :: (OI -> ()) -> OI -> ThreadId
forkIO act u =  let !(u1:u2:u3:_) = parts u in
                let t             = act u1 in
                case attachThreadId t u2 of
                  Nothing -> itsThreadId t u3
                  Just i  -> par t i

-- Atravers Tue Apr 20 06:04:10 UTC 2021