Difference between revisions of "Talk:Parallelism vs. Concurrency"

From HaskellWiki
Jump to: navigation, search
m (Type error corrected)
(Using unsafeInterleaveIO where applicable)
Line 32: Line 32:
 
|}
 
|}
   
* Now have a look at this <del>equally-as-ugly</del> prototype definition for <del><code>spawnIO</code></del> <code>forkIO</code>:
+
* Now have a look at this <del>nearly-as-ugly</del> prototype definition for <del><code>spawnIO</code></del> <code>forkIO</code>:
 
:{|
 
:{|
 
|<haskell>
 
|<haskell>
 
forkIO :: IO () -> IO ThreadId
 
forkIO :: IO () -> IO ThreadId
forkIO act = do let t = unsafeLocalState act
+
forkIO act = do t <- unsafeInterleaveIO act
 
case par t () of
 
case par t () of
 
!_ -> do i' <- itsThreadId t
 
!_ -> do i' <- itsThreadId t
Line 64: Line 64:
 
Can either of these prototypes ever go mainstream?
 
Can either of these prototypes ever go mainstream?
 
* As shown by it's type signature, <code>par</code> is supposed to be pure: avoiding the use of <code>unsafeLocalState</code> means making it primitive;
 
* As shown by it's type signature, <code>par</code> is supposed to be pure: avoiding the use of <code>unsafeLocalState</code> means making it primitive;
* Considering it's already <code>IO</code>-based, <code>forkIO</code> without <code>unsafeLocalState</code> seems more likely.
 
  +
* While the use of <code>unsafeInterleaveIO</code> may annoy some, it being one of the earlier Haskell extensions means it's widely available.
   
[[IO, partible-style|This]] looks interesting:
 
  +
For now, using a primitive <code>par</code> (and others) to define <code>forkIO</code> looks like the simplest option...but if using <code>unsafeInterleaveIO</code> ''really does'' annoy you, how about [[IO, partible-style|this]]:
<haskell>
 
  +
 
:<haskell>
 
forkIO :: (OI -> ()) -> OI -> ThreadId
 
forkIO :: (OI -> ()) -> OI -> ThreadId
 
forkIO act u = let !(u1:u2:u3:_) = parts u in
 
forkIO act u = let !(u1:u2:u3:_) = parts u in

Revision as of 21:51, 20 April 2021

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 (evalIO x >> return ()))
           of
             !_ -> y

where:

      evalIO :: 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 evalIO to start evaluating x.

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

  • 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 par t () of
                   !_ -> do i' <- itsThreadId t
                            case i' of
                              Just i  -> return i
                              Nothing -> ioError "forkIO"

where:

      itsThreadId :: a -> IO (Maybe ThreadId)

Assuming:

  • par and itsThreadId are primitive,
  • itsThreadId would return Nothing if it's argument had not been previously used by par;

then:

  1. Evaluating par t () causes a new ThreadId to be attached to t by the implementation;
  2. itsThreadId retrieves i', the (possible) identifier for t;
  3. forkIO then extracts and returns the identifier.

This looks very much like elementary concurrency: parallelism, but having visible side effects.

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 par t () of
                  !_ -> case itsThreadId t u2 of
                          Just i  -> i
                          Nothing -> ioError "forkIO" u3

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