Difference between revisions of "Talk:Parallelism vs. Concurrency"
Jump to navigation
Jump to search
m (More changes to examples) |
|||
(One intermediate revision by the same user not shown) | |||
Line 1: | Line 1: | ||
=== Parallelism vs concurrency: what's the difference? === |
=== Parallelism vs concurrency: what's the difference? === |
||
− | + | <b><i>Visible</i> side effects:</b> |
|
* Have a look at this <del>ugly eysore</del> "prototype definition" of <code>par</code>: |
* Have a look at this <del>ugly eysore</del> "prototype definition" of <code>par</code>: |
||
Line 21: | Line 21: | ||
Assuming: |
Assuming: |
||
− | * <code>x</code> is |
+ | * <code>x</code> is <i>well-defined</i> (it contains no <code>unsafe...</code> calls), |
− | * <code>x</code> is |
+ | * <code>x</code> is <i>well-behaved</i> (not throwing exceptions or causing errors); |
then: |
then: |
||
Line 36: | Line 36: | ||
|<haskell> |
|<haskell> |
||
forkIO :: IO () -> IO ThreadId |
forkIO :: IO () -> IO ThreadId |
||
− | forkIO act = do |
+ | forkIO act = do v <- newEmptyMVar |
− | + | let thr = do i <- myThreadId |
|
− | + | putMVar v i |
|
− | + | act |
|
+ | z <- unsafeInterleaveIO thr |
||
+ | par z (takeMVar v) |
||
</haskell> |
</haskell> |
||
Line 45: | Line 47: | ||
<haskell> |
<haskell> |
||
− | + | myThreadId :: IO ThreadId |
|
− | + | newEmptyMVar :: IO (MVar a) |
|
+ | putMVar :: MVar a -> a -> IO () |
||
+ | takeMVar :: MVar a -> IO a |
||
+ | par :: a -> b -> b |
||
</haskell> |
</haskell> |
||
+ | Assuming <code>par</code>, <code>newEmptyMVar</code>, <code>putMVar</code> |
||
− | Assuming: |
||
− | + | and <code>takeMVar</code> are primitive, |
|
− | * <code>attachThreadId</code> would return <code>Nothing</code> if it's argument already has been assigned a <code>ThreadId</code>; |
||
then: |
then: |
||
− | # |
+ | # An unused <code>MVar</code> is obtained: <code>v</code>; |
− | # |
+ | # the parameter <code>act</code> is used to build <code>thr</code> which will store its <code>ThreadId</code> in <code>v</code>; |
− | # |
+ | # <code>z</code>, the future result of <code>thr</code>, is then retrieved lazily by using <code>unsafeInterleaveIO</code>; |
+ | # <code>par</code> then presents <code>z</code> for parallel evaluation; |
||
− | # Some time later, the implementation discovers that a <code>ThreadId</code> has been attached to <code>t</code> and uses the <code>ThreadId</code> to immediately start a new thread for evaluating <code>t</code>; |
||
+ | # </code>putMVar</code> waits while <code>v</code> is empty; |
||
− | |||
+ | # </code>putMVar</code> then returns the contents of <code>v</code>. |
||
+ | |||
This is parallelism, but having visible side effects - it behaves like elementary concurrency. |
This is parallelism, but having visible side effects - it behaves like elementary concurrency. |
||
|} |
|} |
||
Line 66: | Line 72: | ||
* While the use of <code>unsafeInterleaveIO</code> may annoy some, it being one of the earlier Haskell extensions means it's widely available. |
* While the use of <code>unsafeInterleaveIO</code> may annoy some, it being one of the earlier Haskell extensions means it's widely available. |
||
− | 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> |
+ | 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> <i>really does</i> annoy you, how about [[IO in action|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:u4:u5:_) = parts u in |
− | let |
+ | 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) |
||
</haskell> |
</haskell> |
||
Latest revision as of 20:14, 2 January 2024
Parallelism vs concurrency: what's the difference?
Visible side effects:
- Have a look at this
ugly eysore"prototype definition" ofpar
:
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 nounsafe...
calls),x
is well-behaved (not throwing exceptions or causing errors);
then:
forkIO
attaches aThreadId
to its argument, adds it to the work-queue and returns the identifier;par
then returnsy
;- Some time later,
forkIO
's argument is called, causingevaluate
to start evaluatingx
.
If
y
is still being evaluated when the evaluation ofx
commences, then we have concurrency, but with no visible side-effects - it behaves like elementary parallelism.
- Now have a look at this
nearly-as-uglyprototype definition forspawnIO
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
andtakeMVar
are primitive,then:
- An unused
MVar
is obtained:v
; - the parameter
act
is used to buildthr
which will store itsThreadId
inv
; z
, the future result ofthr
, is then retrieved lazily by usingunsafeInterleaveIO
;par
then presentsz
for parallel evaluation;- putMVar waits while
v
is empty; - putMVar then returns the contents of
v
.
This is parallelism, but having visible side effects - it behaves like elementary concurrency.
- An unused
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 ofunsafeLocalState
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