Difference between revisions of "Talk:Parallelism vs. Concurrency"
Jump to navigation
Jump to search
m (Type error corrected) |
m (Observations clarified) |
||
(3 intermediate revisions by the same user not shown) | |||
Line 8: | Line 8: | ||
par :: a -> b -> b |
par :: a -> b -> b |
||
par x y = case |
par x y = case |
||
− | unsafeLocalState (forkIO ( |
+ | unsafeLocalState (forkIO (evaluate x >> return ())) |
of |
of |
||
!_ -> y |
!_ -> y |
||
Line 16: | Line 16: | ||
<haskell> |
<haskell> |
||
− | + | evaluate :: a -> IO a |
|
− | forkIO :: IO () -> IO ThreadId |
+ | forkIO :: IO () -> IO ThreadId |
</haskell> |
</haskell> |
||
Line 27: | Line 27: | ||
# <code>forkIO</code> attaches a <code>ThreadId</code> to its argument, adds it to the work-queue and returns the identifier; |
# <code>forkIO</code> attaches a <code>ThreadId</code> to its argument, adds it to the work-queue and returns the identifier; |
||
# <code>par</code> then returns <code>y</code>; |
# <code>par</code> then returns <code>y</code>; |
||
− | # Some time later, <code>forkIO</code>'s argument is called, causing <code> |
+ | # Some time later, <code>forkIO</code>'s argument is called, causing <code>evaluate</code> to start evaluating <code>x</code>. |
− | If <code>y</code> is still being evaluated when the evaluation of <code>x</code> commences, then we have |
+ | If <code>y</code> is still being evaluated when the evaluation of <code>x</code> commences, then we have concurrency, but with no visible side-effects - elementary parallelism. |
|} |
|} |
||
− | * Now have a look at this <del> |
+ | * 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 |
+ | forkIO act = do t <- unsafeInterleaveIO act |
− | case |
+ | case attachThreadId t of |
− | + | Nothing -> itsThreadId t |
|
− | + | Just i -> par t (return i) |
|
− | Just i -> return i |
||
− | Nothing -> ioError "forkIO" |
||
</haskell> |
</haskell> |
||
Line 47: | Line 45: | ||
<haskell> |
<haskell> |
||
− | + | attachThreadId :: a -> IO (Maybe ThreadId) |
|
+ | itsThreadId :: a -> IO ThreadId |
||
</haskell> |
</haskell> |
||
Assuming: |
Assuming: |
||
− | * <code>par</code> and <code>itsThreadId</code> are primitive, |
+ | * <code>par</code>, <code>attachThreadId</code> and <code>itsThreadId</code> are primitive, |
− | * <code> |
+ | * <code>attachThreadId</code> would return <code>Nothing</code> if it's argument already has been assigned a <code>ThreadId</code>; |
then: |
then: |
||
− | # |
+ | # A new <code>ThreadId</code> is assigned to the new (and suspended) value <code>t</code>; |
− | # <code> |
+ | # evaluating <code>par t i</code> causes <code>t</code> to be added to the work-queue by the implementation; |
− | # <code> |
+ | # the <code>ThreadId</code> for <code>t</code> is then returned; |
+ | # 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>; |
||
− | This |
+ | This is parallelism, but having visible side effects - it looks very much like elementary concurrency. |
|} |
|} |
||
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; |
||
+ | * While the use of <code>unsafeInterleaveIO</code> may annoy some, it being one of the earlier Haskell extensions means it's widely available. |
||
− | * Considering it's already <code>IO</code>-based, <code>forkIO</code> without <code>unsafeLocalState</code> seems more likely. |
||
+ | 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]]: |
||
− | [[IO, partible-style|This]] looks interesting: |
||
+ | |||
− | <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 |
||
let t = act u1 in |
let t = act u1 in |
||
− | case |
+ | case attachThreadId t u2 of |
− | + | Nothing -> itsThreadId t u3 |
|
− | + | Just i -> par t i |
|
− | Nothing -> ioError "forkIO" u3 |
||
</haskell> |
</haskell> |
||
Revision as of 20:58, 14 May 2021
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 - elementary parallelism.
- Now have a look at this
nearly-as-uglyprototype definition forspawnIO
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
anditsThreadId
are primitive,attachThreadId
would returnNothing
if it's argument already has been assigned aThreadId
;
then:
- A new
ThreadId
is assigned to the new (and suspended) valuet
; - evaluating
par t i
causest
to be added to the work-queue by the implementation; - the
ThreadId
fort
is then returned; - Some time later, the implementation discovers that a
ThreadId
has been attached tot
and uses theThreadId
to immediately start a new thread for evaluatingt
;
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 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:_) = 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