Talk:Parallelism vs. Concurrency

From HaskellWiki
Revision as of 20:58, 14 May 2021 by Atravers (talk | contribs) (Observations clarified)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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 - 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 looks very much like elementary concurrency.

Can either of these prototypes ever go mainstream?

  • As shown by it's type signature, par is supposed to be pure: 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