Talk:Parallelism vs. Concurrency

From HaskellWiki
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 - 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 v <- newEmptyMVar
                 let thr = do i <- myThreadId
                              putMVar v i
                              act
                 z <- unsafeInterleaveIO thr
                 par z (takeMVar v)

where:

      myThreadId   :: IO ThreadId
      newEmptyMVar :: IO (MVar a)
      putMVar      :: MVar a -> a -> IO ()
      takeMVar     :: MVar a -> IO a
      par          :: a -> b -> b

Assuming par, newEmptyMVar, putMVar and takeMVar are primitive,

then:

  1. An unused MVar is obtained: v;
  2. the parameter act is used to build thr which will store its ThreadId in v;
  3. z, the future result of thr, is then retrieved lazily by using unsafeInterleaveIO;
  4. par then presents z for parallel evaluation;
  5. putMVar waits while v is empty;
  6. putMVar then returns the contents of v.

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:u4:u5:_) = parts u in
                let !v = newEmptyMVar u1
                let z  = let !i = myThreadId u2 in
                         let !_ = putMVar v i u3 in
                         let !_ = act u4 in
                         ()
                in  par z (takeMVar v u5)

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