on master help sign in
Update the docs of async/wAsync/parallel
04afd6fHarendra Kumar
src/Streamly/Internal/Data/Stream/Ahead.hs+38−9
@@ -595,16 +595,43 @@ forkSVarAhead m1 m2 = mkStream $ \st yld sng stp -> do
595
595
596infixr 6 `ahead`
596infixr 6 `ahead`
597
597
598−-- | Appends two streams, some future actions may be evaluated concurrently,
598+-- | Appends two streams, both the streams may be evaluated concurrently but
599−-- ahead of time. The behavior is similar to 'consM' for 'AheadT' streams:
599+-- the outputs are used in the same order as the corresponding actions in the
600+-- original streams, side effects will happen in the order in which the streams
601+-- are evaluated:
602+--
603+-- >>> stream1 = Stream.yieldM (delay 4) -- SerialT IO Int
604+-- >>> stream2 = Stream.yieldM (delay 2) -- SerialT IO Int
605+-- >>> Stream.toList $ stream1 `ahead` stream2 -- IO [Int]
606+-- 2 sec
607+-- 4 sec
608+-- [4,2]
600--
609--
610+-- Multiple streams can be combined. With enough threads, all of them can be
611+-- scheduled simultaneously:
612+--
601−-- >>> stream = Stream.yieldM (delay 2) `ahead` Stream.yieldM (delay 1)
613+-- >>> stream3 = Stream.yieldM (delay 1)
602−-- >>> Stream.toList stream -- IO [Int]
614+-- >>> Stream.toList $ stream1 `ahead` stream2 `ahead` stream3
603-- 1 sec
615-- 1 sec
604-- 2 sec
616-- 2 sec
617+-- 4 sec
605−-- [2,1]
618+-- [4,2,1]
606--
619--
620+-- With 2 threads, only two can be scheduled at a time, when one of those
621+-- finishes, the third one gets scheduled:
622+--
623+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `ahead` stream2 `ahead` stream3
624+-- 2 sec
625+-- 1 sec
626+-- 4 sec
627+-- [4,2,1]
628+--
629+-- Only streams are scheduled for ahead evaluation, how actions within a stream
630+-- are evaluated depends on the stream type. If it is a concurrent stream they
631+-- will be evaluated concurrently. It may not make much sense combining serial
632+-- streams using 'ahead'.
633+--
607−-- This operation can be used to fold an infinite lazy container of streams.
634+-- 'ahead' can be safely used to fold an infinite lazy container of streams.
608--
635--
609-- /Since: 0.3.0 ("Streamly")/
636-- /Since: 0.3.0 ("Streamly")/
610--
637--
@@ -640,9 +667,6 @@ consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)
640-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.ahead'
667-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.ahead'
641-- @
668-- @
642--
669--
643−-- Any exceptions generated by concurrent evaluation are propagated to the
644−-- output stream as soon as they occur.
645−--
646-- A single 'Monad' bind behaves like a @for@ loop with iterations executed
670-- A single 'Monad' bind behaves like a @for@ loop with iterations executed
647-- concurrently, ahead of time, producing side effects of iterations out of
671-- concurrently, ahead of time, producing side effects of iterations out of
648-- order, but results in order:
672-- order, but results in order:
@@ -671,6 +695,11 @@ consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)
671-- 6 sec
695-- 6 sec
672-- [3,5,4,6]
696-- [3,5,4,6]
673--
697--
698+-- The behavior can be explained as follows. All the iterations corresponding
699+-- to the element @1@ in the first stream constitute one output stream and all
700+-- the iterations corresponding to @2@ constitute another output stream and
701+-- these two output streams are merged using 'ahead'.
702+--
674-- /Since: 0.3.0 ("Streamly")/
703-- /Since: 0.3.0 ("Streamly")/
675--
704--
676-- @since 0.8.0
705-- @since 0.8.0
src/Streamly/Internal/Data/Stream/Async.hs+183−52
@@ -634,9 +634,55 @@ joinStreamVarAsync style m1 m2 = mkStream $ \st yld sng stp ->
634
634
635infixr 6 `async`
635infixr 6 `async`
636
636
637−-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.
637+-- | Merges two streams, both the streams may be evaluated concurrently,
638−-- Merges two streams possibly concurrently, preferring the
638+-- outputs from both are used as they arrive:
639−-- elements from the left one when available.
639+--
640+-- >>> stream1 = Stream.yieldM (delay 4)
641+-- >>> stream2 = Stream.yieldM (delay 2)
642+-- >>> Stream.toList $ stream1 `async` stream2
643+-- [4,2]
644+--
645+-- Multiple streams can be combined. With enough threads, all of them can be
646+-- scheduled simultaneously:
647+--
648+-- >>> stream3 = Stream.yieldM (delay 1)
649+-- >>> Stream.toList $ stream1 `async` stream2 `async` stream3
650+-- [1,2,4]
651+--
652+-- With 2 threads, only two can be scheduled at a time, when one of those
653+-- finishes, the third one gets scheduled:
654+--
655+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3
656+-- [2,1,4]
657+--
658+-- With a single thread, it becomes serial:
659+--
660+-- >>> Stream.toList $ Stream.maxThreads 1 $ stream1 `async` stream2 `async` stream3
661+-- [4,2,1]
662+--
663+-- Only streams are scheduled for async evaluation, how actions within a
664+-- stream are evaluated depends on the stream type. If it is a concurrent
665+-- stream they will be evaluated concurrently.
666+--
667+-- In the following example, both the streams are scheduled for concurrent
668+-- evaluation but each individual stream is evaluated serially:
669+--
670+-- >>> stream1 = Stream.fromListM $ map delay [3,3] -- SerialT IO Int
671+-- >>> stream2 = Stream.fromListM $ map delay [1,1] -- SerialT IO Int
672+-- >>> Stream.toList $ stream1 `async` stream2 -- IO [Int]
673+-- [1,1,3,3]
674+--
675+-- If total threads are 2, the third stream is scheduled only after one of the
676+-- first two has finished:
677+--
678+-- >>> stream3 = Stream.fromListM $ map delay [2,2] -- SerialT IO Int
679+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3 -- IO [Int]
680+-- [1,1,3,2,3,2]
681+--
682+-- Thus 'async' goes deep in first few streams rather than going wide in all
683+-- streams. It prefers to evaluate the leftmost streams as much as possible.
684+-- Because of this behavior, 'async' can be safely used to fold an infinite
685+-- lazy container of streams.
640--
686--
641-- /Since: 0.2.0 ("Streamly")/
687-- /Since: 0.2.0 ("Streamly")/
642--
688--
@@ -668,51 +714,45 @@ consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
668-- AsyncT
714-- AsyncT
669------------------------------------------------------------------------------
715------------------------------------------------------------------------------
670
716
671−-- | The 'Semigroup' operation (@<>@) for 'AsyncT' merges two streams
717+-- | For 'AsyncT' streams:
672−-- concurrently with priority given to the first stream. In @s1 <> s2 <> s3
673−-- ...@ the streams s1, s2 and s3 are scheduled for execution in that order.
674−-- Multiple scheduled streams may be executed concurrently and the elements
675−-- generated by them are served to the consumer as and when they become
676−-- available. This behavior is similar to the scheduling and execution behavior
677−-- of actions in a single async stream.
678−--
679−-- Since only a finite number of streams are executed concurrently, this
680−-- operation can be used to fold an infinite lazy container of streams.
681−--
682−-- @
683−-- import qualified "Streamly.Prelude" as S
684−-- import Control.Concurrent
685--
718--
686−-- main = (S.toList . S.'asyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
687−-- @
688-- @
719-- @
689−-- [1,2,3,4]
720+-- (<>) = 'Streamly.Prelude.async'
721+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.async'
690-- @
722-- @
691--
723--
692−-- Any exceptions generated by a constituent stream are propagated to the
724+-- A single 'Monad' bind behaves like a @for@ loop with iterations of the loop
693−-- output stream. The output and exceptions from a single stream are guaranteed
725+-- executed concurrently a la the 'async' combinator, producing results and
694−-- to arrive in the same order in the resulting stream as they were generated
726+-- side effects of iterations out of order:
695−-- in the input stream. However, the relative ordering of elements from
696−-- different streams in the resulting stream can vary depending on scheduling
697−-- and generation delays.
698--
727--
699−-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration
728+-- >>> :{
700−-- concurrently based on demand. More concurrent iterations are started only
729+-- Stream.toList $ Stream.asyncly $ do
701−-- if the previous iterations are not able to produce enough output for the
730+-- x <- Stream.fromList [2,1] -- foreach x in stream
702−-- consumer.
731+-- Stream.yieldM $ delay x
732+-- :}
733+-- 1 sec
734+-- 2 sec
735+-- [1,2]
703--
736--
704−-- @
737+-- Nested monad binds behave like nested @for@ loops with nested iterations
705−-- main = S.'drain' . S.'asyncly' $ do
738+-- executed concurrently, a la the 'async' combinator:
706−-- n <- return 3 \<\> return 2 \<\> return 1
739+--
740+-- >>> :{
741+-- Stream.toList $ Stream.asyncly $ do
742+-- x <- Stream.fromList [1,2] -- foreach x in stream
743+-- y <- Stream.fromList [2,4] -- foreach y in stream
707−-- S.yieldM $ do
744+-- Stream.yieldM $ delay (x + y)
708−-- threadDelay (n * 1000000)
745+-- :}
709−-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
746+-- 3 sec
710−-- @
747+-- 4 sec
711−-- @
748+-- 5 sec
712−-- ThreadId 40: Delay 1
749+-- 6 sec
713−-- ThreadId 39: Delay 2
750+-- [3,4,5,6]
714−-- ThreadId 38: Delay 3
751+--
715−-- @
752+-- The behavior can be explained as follows. All the iterations corresponding
753+-- to the element @1@ in the first stream constitute one output stream and all
754+-- the iterations corresponding to @2@ constitute another output stream and
755+-- these two output streams are merged using 'async'.
716--
756--
717-- /Since: 0.1.0 ("Streamly")/
757-- /Since: 0.1.0 ("Streamly")/
718--
758--
@@ -817,28 +857,119 @@ consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
817
857
818infixr 6 `wAsync`
858infixr 6 `wAsync`
819
859
820−-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.
860+-- | For singleton streams, 'wAsync' is the same as 'async'. See 'async' for
821−-- Merges two streams concurrently choosing elements from both fairly.
861+-- singleton stream behavior. For multi-element streams, while 'async' is left
862+-- biased i.e. it tries to evaluate the left side stream as much as possible,
863+-- 'wAsync' tries to schedule them both fairly. In other words, 'async' goes
864+-- deep while 'wAsync' goes wide. However, outputs are always used as they
865+-- arrive.
866+--
867+-- With a single thread, 'async' starts behaving like 'serial' while 'wAsync'
868+-- starts behaving like 'wSerial'.
869+--
870+-- >>> stream1 = Stream.fromList [1,2,3]
871+-- >>> stream2 = Stream.fromList [4,5,6]
872+-- >>> Stream.toList $ Stream.asyncly $ Stream.maxThreads 1 $ stream1 `async` stream2
873+-- [1,2,3,4,5,6]
874+--
875+-- >>> Stream.toList $ Stream.wAsyncly $ Stream.maxThreads 1 $ stream1 `wAsync` stream2
876+-- [1,4,2,5,3,6]
877+--
878+-- With two threads available, and combining three streams:
879+--
880+-- >>> stream3 = Stream.fromList [7,8,9]
881+-- >>> Stream.toList $ Stream.asyncly $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3
882+-- [1,2,3,4,5,6,7,8,9]
883+--
884+-- >>> Stream.toList $ Stream.wAsyncly $ Stream.maxThreads 2 $ stream1 `wAsync` stream2 `wAsync` stream3
885+-- [1,4,2,7,5,3,8,6,9]
886+--
887+-- This operation cannot be used to fold an infinite lazy container of streams,
888+-- because it schedules all the streams in a round robin manner.
889+--
890+-- Note that 'WSerialT' and single threaded 'WAsyncT' both interleave streams
891+-- but the exact scheduling is slightly different in both cases.
822--
892--
823-- @since 0.8.0
893-- @since 0.8.0
824--
894--
825-- /Since: 0.2.0 ("Streamly")/
895-- /Since: 0.2.0 ("Streamly")/
896+
897+-- Scheduling details:
898+--
899+-- This is how the execution of the above example proceeds:
900+--
901+-- 1. The scheduler queue is initialized with @[S.fromList [1,2,3],
902+-- (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])]@ assuming the head of the
903+-- queue is represented by the rightmost item.
904+-- 2. @S.fromList [1,2,3]@ is executed, yielding the element @1@ and putting
905+-- @[2,3]@ at the back of the scheduler queue. The scheduler queue now looks
906+-- like @[(S.fromList [4,5,6]) \<> (S.fromList [7,8,9]), S.fromList [2,3]]@.
907+-- 3. Now @(S.fromList [4,5,6]) \<> (S.fromList [7,8,9])@ is picked up for
908+-- execution, @S.fromList [7,8,9]@ is added at the back of the queue and
909+-- @S.fromList [4,5,6]@ is executed, yielding the element @4@ and adding
910+-- @S.fromList [5,6]@ at the back of the queue. The queue now looks like
911+-- @[S.fromList [2,3], S.fromList [7,8,9], S.fromList [5,6]]@.
912+-- 4. Note that the scheduler queue expands by one more stream component in
913+-- every pass because one more @<>@ is broken down into two components. At this
914+-- point there are no more @<>@ operations to be broken down further and the
915+-- queue has reached its maximum size. Now these streams are scheduled in
916+-- round-robin fashion yielding @[2,7,5,3,8,6,9]@.
917+--
918+-- As we see above, in a right associated expression composed with @<>@, only
919+-- one @<>@ operation is broken down into two components in one execution,
920+-- therefore, if we have @n@ streams composed using @<>@ it will take @n@
921+-- scheduler passes to expand the whole expression. By the time @n-th@
922+-- component is added to the scheduler queue, the first component would have
923+-- received @n@ scheduler passes.
924+--
826{-# INLINE wAsync #-}
925{-# INLINE wAsync #-}
827wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
926wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
828wAsync = joinStreamVarAsync WAsyncVar
927wAsync = joinStreamVarAsync WAsyncVar
829
928
830−-- | 'WAsyncT' is similar to 'WSerialT' but with concurrent execution.
929+-- | For 'WAsyncT' streams:
831−-- The 'Semigroup' operation (@<>@) for 'WAsyncT' merges two streams
930+--
832−-- concurrently interleaving the actions from both the streams. In @s1
931+-- @
833−-- <> s2 <> s3 ...@, the individual actions from streams @s1@, @s2@ and @s3@
932+-- (<>) = 'Streamly.Prelude.wAsync'
834−-- are scheduled for execution in a round-robin fashion. Multiple scheduled
933+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wAsync'
934+-- @
935+--
936+-- A single 'Monad' bind behaves like a @for@ loop with iterations of the loop
835−-- actions may be executed concurrently, the results from concurrent executions
937+-- executed concurrently a la the 'wAsync' combinator, producing results and
836−-- are consumed in the order in which they become available.
938+-- side effects of iterations out of order:
837--
939--
940+-- >>> :{
941+-- Stream.toList $ Stream.wAsyncly $ do
942+-- x <- Stream.fromList [2,1] -- foreach x in stream
943+-- Stream.yieldM $ delay x
944+-- :}
945+-- 1 sec
946+-- 2 sec
947+-- [1,2]
948+--
949+-- Nested monad binds behave like nested @for@ loops with nested iterations
950+-- executed concurrently, a la the 'wAsync' combinator:
951+--
952+-- >>> :{
953+-- Stream.toList $ Stream.wAsyncly $ do
954+-- x <- Stream.fromList [1,2] -- foreach x in stream
955+-- y <- Stream.fromList [2,4] -- foreach y in stream
956+-- Stream.yieldM $ delay (x + y)
957+-- :}
958+-- 3 sec
959+-- 4 sec
960+-- 5 sec
961+-- 6 sec
962+-- [3,4,5,6]
963+--
964+-- The behavior can be explained as follows. All the iterations corresponding
965+-- to the element @1@ in the first stream constitute one 'WAsyncT' output
966+-- stream and all the iterations corresponding to @2@ constitute another
967+-- 'WAsyncT' output stream and these two output streams are merged using
968+-- 'wAsync'.
838--
969--
839-- The @W@ in the name stands for @wide@ or breadth wise scheduling in
970-- The @W@ in the name stands for @wide@ or breadth wise scheduling in
840-- contrast to the depth wise scheduling behavior of 'AsyncT'.
971-- contrast to the depth wise scheduling behavior of 'AsyncT'.
841−--
972+
842-- @
973-- @
843-- import qualified "Streamly.Prelude" as S
974-- import qualified "Streamly.Prelude" as S
844-- import Control.Concurrent
975-- import Control.Concurrent
src/Streamly/Internal/Data/Stream/Parallel.hs+28−102
@@ -222,48 +222,27 @@ consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
222
222
223infixr 6 `parallel`
223infixr 6 `parallel`
224
224
225−-- | Execute two streams concurrently and merge their outputs. For example, if
225+-- | Like 'Streamly.Prelude.async' except that the execution is much more
226−-- stream @a@ is a serial stream consisting of @a1, a2, a3@ and stream @b@ is a
226+-- strict. There is no limit on the number of threads. While
227−-- serial stream consisting of @b1, b2, b3@ then stream @a `parallel` b@ may
227+-- 'Streamly.Prelude.async' may not schedule a stream if there is no demand
228−-- produce @a1, b1, a2, b2, a3, b3@ or @a1, a2, b1, a3, b2, b3@ or some other
228+-- from the consumer, 'parallel' always evaluates both the streams immediately.
229−-- combination depending on the rate at which the two streams produce elements.
229+-- The only limit that applies to 'parallel' is 'Streamly.Prelude.maxBuffer'.
230−-- However, the relative order of outputs from a single stream e.g. @a1, a2,
230+-- Evaluation may block if the output buffer becomes full.
231−-- a3@ would remain the same in the resulting stream. The effects in the two
231+--
232−-- streams may occur concurrently.
232+-- >>> stream = Stream.yieldM (delay 2) `parallel` Stream.yieldM (delay 1)
233−--
233+-- >>> Stream.toList stream -- IO [Int]
234−-- To run single actions (instead of streams) in parallel wrap them into
234+-- 1 sec
235−-- singleton streams. The following trivial example is semantically equivalent
235+-- 2 sec
236−-- to running the action @putStrLn "hello"@ in the current thread:
236+-- [1,2]
237−--
237+--
238−-- >>> Stream.toList $ Stream.yieldM (putStrLn "hello") `Stream.parallel` Stream.nil
238+-- 'parallel' guarantees that all the streams are scheduled for execution
239−-- hello
239+-- immediately, therefore, we could use things like starting timers inside the
240−-- [()]
240+-- streams and relying on the fact that all timers were started at the same
241−--
241+-- time.
242−-- Run two actions concurrently:
243−--
244−-- >>> import Control.Concurrent (threadDelay)
245−-- >>> Stream.toList $ Stream.yieldM (putStrLn "hello") `Stream.parallel` Stream.yieldM (threadDelay 100000 >> putStrLn "world")
246−-- hello
247−-- world
248−-- [(),()]
249−--
250−-- Run effects concurrently, disregarding their outputs:
251−--
252−-- >>> Stream.toList $ nilM (putStrLn "hello") `parallel` Stream.nilM (threadDelay 100000 >> putStrLn "world")
253−-- hello
242+--
254−-- world
243+-- Unlike 'async' this operation cannot be used to fold an infinite lazy
255−-- []
244+-- container of streams, because it schedules all the streams strictly
256−--
245+-- concurrently.
257−-- Run an effectful action, and a pure effect without any output, concurrently:
258−--
259−-- >>> Stream.toList $ Stream.yieldM (return 1) `Stream.parallel` Stream.nilM (putStrLn "world")
260−-- world
261−-- [1]
262−--
263−-- Note that 'parallel' is a polymorphic version of the @Semigroup@ operation
264−-- @<>@ of 'ParallelT'.
265−--
266−-- `nilM` is currently @Internal@.
267--
246--
268-- /Since: 0.2.0 ("Streamly")/
247-- /Since: 0.2.0 ("Streamly")/
269--
248--
@@ -371,7 +350,7 @@ mkParallel = D.fromStreamD . mkParallelD . D.toStreamD
371--
350--
372-- | Redirect a copy of the stream to a supplied fold and run it concurrently
351-- | Redirect a copy of the stream to a supplied fold and run it concurrently
373-- in an independent thread. The fold may buffer some elements. The buffer size
352-- in an independent thread. The fold may buffer some elements. The buffer size
374−-- is determined by the prevailing 'maxBuffer' setting.
353+-- is determined by the prevailing 'Streamly.Prelude.maxBuffer' setting.
375--
354--
376-- @
355-- @
377-- Stream m a -> m b
356-- Stream m a -> m b
@@ -481,69 +460,16 @@ distributeAsync_ = flip (foldr tapAsync)
481-- ParallelT
460-- ParallelT
482------------------------------------------------------------------------------
461------------------------------------------------------------------------------
483
462
484−-- | Async composition with strict concurrent execution of all streams.
485−--
486−-- The 'Semigroup' instance of 'ParallelT' executes both the streams
487−-- concurrently without any delay or without waiting for the consumer demand
488−-- and /merges/ the results as they arrive. If the consumer does not consume
489−-- the results, they are buffered upto a configured maximum, controlled by the
490−-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will
491−-- block until there is space in the buffer.
492−--
493−-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a
494−-- round robin fashion. The key difference is that 'WAsyncT' might wait for the
495−-- consumer demand before it executes the tasks whereas 'ParallelT' starts
496−-- executing all the tasks immediately without waiting for the consumer demand.
497−-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does
498−-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is
499−-- strict.
500−--
501−-- 'ParallelT' is useful for cases when the streams are required to be
463+-- | For 'ParallelT' streams:
502−-- evaluated simultaneously irrespective of how the consumer consumes them e.g.
503−-- when we want to race two tasks and want to start both strictly at the same
504−-- time or if we have timers in the parallel tasks and our results depend on
505−-- the timers being started at the same time. If we do not have such
506−-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more
507−-- efficient than 'ParallelT'.
508--
464--
509-- @
465-- @
510−-- main = (S.'toList' . S.'parallely' $ (S.fromFoldable [1,2]) \<> (S.fromFoldable [3,4])) >>= print
466+-- (<>) = 'Streamly.Prelude.parallel'
511−-- @
467+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.parallel'
512−-- @
513−-- [1,3,2,4]
514−-- @
515−--
516−-- When streams with more than one element are merged, it yields whichever
517−-- stream yields first without any bias, unlike the 'Async' style streams.
518−--
519−-- Any exceptions generated by a constituent stream are propagated to the
520−-- output stream. The output and exceptions from a single stream are guaranteed
521−-- to arrive in the same order in the resulting stream as they were generated
522−-- in the input stream. However, the relative ordering of elements from
523−-- different streams in the resulting stream can vary depending on scheduling
524−-- and generation delays.
525−--
526−-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
527−-- of the loop concurrently.
528−--
529−-- @
530−-- import qualified "Streamly.Prelude" as S
531−-- import Control.Concurrent
532−--
533−-- main = S.'drain' . S.'parallely' $ do
534−-- n <- return 3 \<\> return 2 \<\> return 1
535−-- S.yieldM $ do
536−-- threadDelay (n * 1000000)
537−-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
538−-- @
539−-- @
540−-- ThreadId 40: Delay 1
541−-- ThreadId 39: Delay 2
542−-- ThreadId 38: Delay 3
543-- @
468-- @
544--
469--
545−-- Note that parallel composition can only combine a finite number of
470+-- See 'Streamly.Prelude.AsyncT', 'ParallelT' is similar except that all
546−-- streams as it needs to retain state for each unfinished stream.
471+-- iterations are strictly concurrent while in 'AsyncT' it depends on the
472+-- consumer demand and available threads. See 'parallel' for more details.
547--
473--
548-- /Since: 0.1.0 ("Streamly")/
474-- /Since: 0.1.0 ("Streamly")/
549--
475--
src/Streamly/Internal/Data/Stream/Serial.hs+16−11
@@ -90,8 +90,8 @@ import Prelude hiding (map, mapM, errorWithoutStackTrace)
90-- | For 'SerialT' streams:
90-- | For 'SerialT' streams:
91--
91--
92-- @
92-- @
93−-- (<>) = 'Streamly.Prelude.serial'
93+-- (<>) = 'Streamly.Prelude.serial' -- 'Semigroup'
94−-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.serial'
94+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.serial' -- 'Monad'
95-- @
95-- @
96--
96--
97-- A single 'Monad' bind behaves like a @for@ loop:
97-- A single 'Monad' bind behaves like a @for@ loop:
@@ -251,8 +251,8 @@ TRAVERSABLE_INSTANCE(SerialT)
251-- | For 'WSerialT' streams:
251-- | For 'WSerialT' streams:
252--
252--
253-- @
253-- @
254−-- (<>) = 'Streamly.Prelude.wSerial'
254+-- (<>) = 'Streamly.Prelude.wSerial' -- 'Semigroup'
255−-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wSerial'
255+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wSerial' -- 'Monad'
256-- @
256-- @
257--
257--
258-- Note that '<>' is associative only if we disregard the ordering of elements
258-- Note that '<>' is associative only if we disregard the ordering of elements
@@ -360,6 +360,18 @@ infixr 6 `wSerial`
360-- >>> Stream.toList $ Stream.wSerially $ stream1 `wSerial` stream2
360-- >>> Stream.toList $ Stream.wSerially $ stream1 `wSerial` stream2
361-- [1,3,2,4]
361-- [1,3,2,4]
362--
362--
363+-- Note, for singleton streams 'wSerial' and 'serial' are identical.
364+--
365+-- Note that this operation cannot be used to fold a container of infinite
366+-- streams but it can be used for very large streams as the state that it needs
367+-- to maintain is proportional to the logarithm of the number of streams.
368+--
369+-- @since 0.8.0
370+--
371+-- /Since: 0.2.0 ("Streamly")/
372+
373+-- Scheduling Notes:
374+--
363-- Note that evaluation of @a \`wSerial` b \`wSerial` c@ does not interleave
375-- Note that evaluation of @a \`wSerial` b \`wSerial` c@ does not interleave
364-- @a@, @b@ and @c@ with equal priority. This expression is equivalent to @a
376-- @a@, @b@ and @c@ with equal priority. This expression is equivalent to @a
365-- \`wSerial` (b \`wSerial` c)@, therefore, it fairly interleaves @a@ with the
377-- \`wSerial` (b \`wSerial` c)@, therefore, it fairly interleaves @a@ with the
@@ -369,13 +381,6 @@ infixr 6 `wSerial`
369-- priority as the rest of the streams taken together. The same is true for
381-- priority as the rest of the streams taken together. The same is true for
370-- each subexpression on the right.
382-- each subexpression on the right.
371--
383--
372−-- Note that this operation cannot be used to fold a container of infinite
373−-- streams but it can be used for very large streams as the state that it needs
374−-- to maintain is proportional to the logarithm of the number of streams.
375−--
376−-- @since 0.8.0
377−--
378−-- /Since: 0.2.0 ("Streamly")/
379{-# INLINE wSerial #-}
384{-# INLINE wSerial #-}
380wSerial :: IsStream t => t m a -> t m a -> t m a
385wSerial :: IsStream t => t m a -> t m a -> t m a
381wSerial m1 m2 = mkStream $ \st yld sng stp -> do
386wSerial m1 m2 = mkStream $ \st yld sng stp -> do
src/Streamly/Prelude.hs+44−59
@@ -33,14 +33,27 @@
33-- >>> Stream.toList stream -- IO [Int]
33-- >>> Stream.toList stream -- IO [Int]
34-- [1,2]
34-- [1,2]
35--
35--
36+-- To demonstrate the serial vs. concurrent execution behavior we will use the
37+-- following blocking IO action that blocks for @n@ seconds:
38+--
39+-- >>> import Control.Concurrent (threadDelay)
40+-- >>> :{
41+-- delay n = do
42+-- threadDelay (n * 1000000) -- sleep for n seconds
43+-- putStrLn (show n ++ " sec") -- print "n sec"
44+-- return n -- IO Int
45+-- :}
46+-- >>> delay 1
47+-- 1 sec
48+--
36−-- 'consM' constructs an effectful stream:
49+-- 'consM' constructs a stream from effectful actions:
37--
50--
38-- @
51-- @
39−-- > stream = 'getLine' \`consM` 'getLine' \`consM` 'nil'
52+-- > stream = delay 1 \`consM` delay 2 \`consM` 'nil'
40-- > Stream.'toList' stream
53-- > Stream.'toList' stream
41−-- hello
54+-- 1 sec
42−-- world
55+-- 2 sec
43−-- ["hello","world"]
56+-- [1,2]
44-- @
57-- @
45--
58--
46-- In the following example, 'repeatM' generates an infinite stream of 'String'
59-- In the following example, 'repeatM' generates an infinite stream of 'String'
@@ -68,17 +81,6 @@
68-- actions forming the stream are executed. Below we will see how 'SerialT',
81-- actions forming the stream are executed. Below we will see how 'SerialT',
69-- 'AsyncT' and 'AheadT' stream types differ from each other.
82-- 'AsyncT' and 'AheadT' stream types differ from each other.
70--
83--
71−-- To demonstrate the serial vs. concurrent execution behavior we will use the
72−-- following blocking action that adds a delay of @n@ seconds:
73−--
74−-- >>> import Control.Concurrent (threadDelay)
75−-- >>> :{
76−-- delay n = do
77−-- threadDelay (n * 1000000) -- sleep for n seconds
78−-- putStrLn (show n ++ " sec") -- print "n sec"
79−-- return n -- IO Int
80−-- :}
81−--
82-- 'SerialT' executes actions serially, so the total delay in the following
84-- 'SerialT' executes actions serially, so the total delay in the following
83-- example is @2 + 1 = 3@ seconds:
85-- example is @2 + 1 = 3@ seconds:
84--
86--
@@ -174,11 +176,12 @@
174--
176--
175-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
177-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
176-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
178-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
177−-- >>> Stream.drain $ stream1 <> stream2
179+-- >>> Stream.toList $ stream1 <> stream2
178-- 1 sec
180-- 1 sec
179-- 2 sec
181-- 2 sec
180-- 3 sec
182-- 3 sec
181-- 4 sec
183-- 4 sec
184+-- [1,2,3,4]
182--
185--
183-- For 'WSerialT', '<>' has an interleaving behavior i.e. it executes one
186-- For 'WSerialT', '<>' has an interleaving behavior i.e. it executes one
184-- action from the first stream and then one action from the second stream and
187-- action from the first stream and then one action from the second stream and
@@ -186,11 +189,12 @@
186--
189--
187-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
190-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
188-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
191-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
189−-- >>> Stream.drain $ Stream.wSerially $ stream1 <> stream2
192+-- >>> Stream.toList $ Stream.wSerially $ stream1 <> stream2
190-- 1 sec
193-- 1 sec
191-- 3 sec
194-- 3 sec
192-- 2 sec
195-- 2 sec
193-- 4 sec
196-- 4 sec
197+-- [1,3,2,4]
194--
198--
195-- The '<>' operation of 'SerialT' and 'WSerialT' is the same as 'serial' and
199-- The '<>' operation of 'SerialT' and 'WSerialT' is the same as 'serial' and
196-- 'wSerial' respectively. The 'serial' combinator combines two streams of any
200-- 'wSerial' respectively. The 'serial' combinator combines two streams of any
@@ -204,13 +208,14 @@
204-- Let's take an example of the `parallel` combinator ('<>' operation of
208-- Let's take an example of the `parallel` combinator ('<>' operation of
205-- 'ParallelT'), it schedules both the streams concurrently.
209-- 'ParallelT'), it schedules both the streams concurrently.
206--
210--
207−-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
211+-- >>> stream1 = Stream.fromListM [delay 3, delay 4]
208−-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
212+-- >>> stream2 = Stream.fromListM [delay 1, delay 2]
209−-- >>> Stream.drain $ stream1 `parallel` stream2
213+-- >>> Stream.toList $ stream1 `parallel` stream2
210-- 1 sec
214-- 1 sec
211-- 2 sec
215-- 2 sec
212-- 3 sec
216-- 3 sec
213-- 4 sec
217-- 4 sec
218+-- [1,2,3,4]
214--
219--
215-- We can use @concatMapWith parallel@ to read concurrently from all incoming
220-- We can use @concatMapWith parallel@ to read concurrently from all incoming
216-- network connections and combine the input streams into a single output
221-- network connections and combine the input streams into a single output
@@ -768,7 +773,14 @@ module Streamly.Prelude
768 -- ** Interleaving
773 -- ** Interleaving
769 , wSerial
774 , wSerial
770
775
771− -- ** Scheduling
776+ -- ** Concurrent Scheduling
777+ -- | Any exceptions generated by concurrent evaluation are propagated to
778+ -- the consumer of the stream as soon as they occur. Exceptions from a
779+ -- particular stream are guaranteed to arrive in the same order in the
780+ -- output stream as they were generated in the input stream.
781+ --
782+ -- See 'maxThreads' and 'maxBuffer' to control the concurrency of these
783+ -- combinators.
772 , ahead
784 , ahead
773 , async
785 , async
774 , wAsync
786 , wAsync
@@ -919,43 +931,16 @@ import Streamly.Internal.Data.Stream.IsStream
919
931
920-- $async
932-- $async
921--
933--
922−-- /Scheduling and execution:/ In an asynchronous stream @a \`consM` b \`consM`
934+-- Asynchronous streams evaluate some future actions concurrently, the results
923−-- c ...@, the actions @a@, @b@, and @c@ are executed concurrently with the
924−-- consumer of the stream. The actions are /scheduled/ for execution in the
925−-- same order as they are specified in the stream. Multiple scheduled actions
926−-- may be /executed/ concurrently in parallel threads of execution. The
927−-- actions may be executed out of order and they may complete at arbitrary
928−-- times. Therefore, the /effects/ of the actions may be observed out of
929−-- order.
930−--
931−-- /Buffering:/ The /results/ from multiple threads of execution are queued in
932−-- a buffer as soon as they become available. The consumer of the stream is
935+-- are given to the consumer as soon as they become available, they may not be
933−-- served from this buffer. Therefore, the consumer may observe the results to
936+-- in the same order as the actions in the stream.
934−-- be out of order. In other words, an asynchronous stream is an unordered
937+--
935−-- stream i.e. order does not matter.
938+-- The results of the actions as well as their side effects are partially
936−--
939+-- ordered. Since the order of elements does not matter the 'Semigroup'
937−-- /Concurrency control:/ Threads are suspended if the `maxBuffer` limit is
940+-- operation is effectively commutative.
938−-- reached, and resumed when the consumer makes space in the buffer. The
939−-- maximum number of concurrent threads depends on `maxThreads`. Number of
940−-- threads is increased or decreased based on the speed of the consumer.
941−--
942−-- /Generation operations:/ Concurrent stream generation operations e.g.
943−-- 'Streamly.Prelude.replicateM' when used in async style schedule and execute
944−-- the stream generating actions in the manner described above. The generation
945−-- actions run concurrently, effects and results of the actions as observed by
946−-- the consumer of the stream may be out of order.
947−--
948−-- /Transformation operations:/ Concurrent stream transformation operations
949−-- e.g. 'Streamly.Prelude.mapM', when used in async style, schedule and
950−-- execute transformation actions in the manner described above. Transformation
951−-- actions run concurrently, effects and results of the actions may be
952−-- observed by the consumer out of order.
953−--
941+--
954−-- /Variants:/ There are two asynchronous stream types 'AsyncT' and 'WAsyncT'.
942+-- There are two asynchronous stream types 'AsyncT' and 'WAsyncT'. They differ
955−-- They are identical with respect to single stream evaluation behavior. Their
943+-- only in the 'Semigroup' and 'Monad' instance behavior.
956−-- behaviors differ in how they combine multiple streams using 'Semigroup' or
957−-- 'Monad' composition. Since the order of elements does not matter in
958−-- asynchronous streams the 'Semigroup' operation is effectively commutative.
959
944
960-- $zipping
945-- $zipping
961--
946--