2-- Module : Streamly.Internal.Data.Stream.Concurrent
3-- Copyright : (c) 2017 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9-- Non-parallelizable stream combinators like unfoldrM, iterateM etc. can be
10-- evaluated concurrently with the stream consumer by using `eval`.
11-- Parallelizable combinators like repeatM, replicateM can generate the stream
12-- concurrently using 'concatMap'.
14-- Single effects related functionality can be moved to
15-- Data.Async/Control.Async.
16-- Common Channel functionality to Data.Channel.
17-- Stream channel to Data.Stream.Channel.
19module Streamly.Internal.Data.Stream.Concurrent
46 -- | Stream combinators using a concurrent channel
49 -- | Evaluates a stream concurrently using a channel.
51 -- Add unfoldrM/iterateM?
54 -- | Uses a single channel to evaluate all actions.
59 -- | Uses a single channel to evaluate all actions.
64 -- | Use a channel for each pair.
72 -- | Shares a single channel across many streams.
81 -- ** Stream of streams
86 -- | Shares a single channel across many streams.
102import Control.Concurrent (myThreadId, killThread)
103import Control.Monad (void, when)
104import Control.Monad.IO.Class (MonadIO(liftIO))
105import Streamly.Internal.Control.Concurrent (MonadAsync, askRunInIO)
106import Streamly.Internal.Control.ForkLifted (forkManaged)
107import Streamly.Internal.Data.Stream.Channel.Dispatcher (modifyThread)
108import Streamly.Internal.Data.Stream.Channel.Types
112import Streamly.Internal.Data.Stream.Channel.Worker (sendWithDoorBell)
113import Streamly.Internal.Data.Stream.StreamD.Type (Stream)
114import Streamly.Internal.Data.Stream.StreamD (Step(..))
116import qualified Streamly.Internal.Data.IORef.Unboxed as Unboxed
117import qualified Streamly.Internal.Data.Stream.StreamD as Stream
118import qualified Streamly.Internal.Data.Stream.StreamD as D
119import qualified Streamly.Internal.Data.Stream.StreamK as K
120import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
122import Prelude hiding (mapM, sequence, concat, concatMap, zipWith)
123import Streamly.Internal.Data.Stream.Concurrent.Channel
127-- Imports for example snippets in this module.
130-- >>> {-# LANGUAGE FlexibleContexts #-}
131-- >>> import Control.Concurrent (threadDelay)
132-- >>> import qualified Streamly.Data.Array as Array
133-- >>> import qualified Streamly.Data.Fold as Fold
134-- >>> import qualified Streamly.Data.Parser as Parser
135-- >>> import qualified Streamly.Internal.Data.Stream as Stream hiding (append2)
136-- >>> import qualified Streamly.Internal.Data.Stream.Concurrent as Stream
137-- >>> import Prelude hiding (concatMap, concat, zipWith)
140-- threadDelay (n * 1000000) -- sleep for n seconds
141-- putStrLn (show n ++ " sec") -- print "n sec"
145-------------------------------------------------------------------------------
146-- Evaluating a stream
147-------------------------------------------------------------------------------
150{-# INLINE_NORMAL parEvalD #-}
151parEvalD :: MonadAsync m => (Config -> Config) -> D.Stream m a -> D.Stream m a
152parEvalD modifier m = D.Stream step Nothing
156 chan <- newChannel modifier
157 sendFirstWorker chan (D.toStreamK m)
158 -- XXX should use an unfold to make this efficient
159 return $ D.Skip $ Just $ fromChannelD chan
161 step gst (Just (D.UnStream step1 st)) = do
164 D.Yield a s -> D.Yield a (Just $ D.Stream step1 s)
165 D.Skip s -> D.Skip (Just $ D.Stream step1 s)
169-- | Evaluate a stream asynchronously. In a serial stream, each element of the
170-- stream is generated as it is demanded by the consumer. `parEval` evaluates
171-- multiple elements of the stream ahead of time and serves the results from a
174-- Note that the evaluation requires only one thread as only one stream needs
175-- to be evaluated. Therefore, the concurrency options that are relevant to
176-- multiple streams won't apply here e.g. maxThreads, eager, interleaved,
177-- ordered, stopWhen options won't have any effect.
179{-# INLINE parEval #-}
180parEval :: MonadAsync m => (Config -> Config) -> Stream m a -> Stream m a
181parEval modifier input = withChannel modifier input (const id)
182 -- Stream.fromStreamD $ parEvalD cfg $ Stream.toStreamD stream
184-------------------------------------------------------------------------------
185-- combining two streams
186-------------------------------------------------------------------------------
188{-# INLINE _appendGeneric #-}
189_appendGeneric :: MonadAsync m =>
190 ((Config -> Config) -> m (Channel m a))
191 -> (Config -> Config)
195_appendGeneric newChan modifier stream1 stream2 = K.concatEffect action
200 chan <- newChan modifier
201 let cfg = modifier defaultConfig
202 done = K.nilM (stopChannel chan)
203 case getStopWhen cfg of
205 toChannelK chan stream2
206 toChannelK chan stream1
208 toChannelK chan stream2
209 toChannelK chan (K.append stream1 done)
211 toChannelK chan (K.append stream2 done)
212 toChannelK chan (K.append stream1 done)
213 return $ Stream.toStreamK $ fromChannel chan
215-- | Create a new channel and add both the streams to it for async evaluation.
216-- The output stream is the result of the evaluation.
217{-# INLINE appendWithK #-}
218appendWithK :: MonadAsync m =>
219 (Config -> Config) -> K.StreamK m a -> K.StreamK m a -> K.StreamK m a
220appendWithK modifier stream1 stream2 =
222 if getOrdered (modifier defaultConfig)
223 then parConcatMapK modifier id (stream1 `K.cons` K.fromPure stream2)
224 else _appendGeneric Append.newChannel modifier stream1 stream2
226 parConcatMapK modifier id (stream1 `K.cons` K.fromPure stream2)
228-- | Evaluate the first stream in the current thread and add the second stream
229-- to the supplied channel. This is to be used by a worker thread.
231-- This can be used with parConcatMap:
234-- concatMap = K.parConcatMap (_appendWithChanK chan) f stream
237{-# INLINE _appendWithChanK #-}
238_appendWithChanK :: MonadAsync m =>
239 Channel m a -> K.StreamK m a -> K.StreamK m a -> K.StreamK m a
240_appendWithChanK chan stream1 stream2 =
241 K.before (toChannelK chan stream2) stream1
243-- | Binary operation to evaluate two streams concurrently using a channel.
245-- If you want to combine more than two streams you almost always want the
246-- 'parList' or `parConcat` operation instead. The performance of this
247-- operation degrades rapidly when more streams are combined as each operation
248-- adds one more concurrent channel. On the other hand, 'parConcat' uses a
249-- single channel for all streams. However, with this operation you can
250-- precisely control the scheduling by creating arbitrary shape expression
255-- >>> parTwo cfg x y = Stream.parList cfg [x, y]
257-- Example, the following code finishes in 4 seconds:
259-- >>> async = Stream.parTwo id
260-- >>> stream1 = Stream.fromEffect (delay 4)
261-- >>> stream2 = Stream.fromEffect (delay 2)
262-- >>> Stream.fold Fold.toList $ stream1 `async` stream2
268parTwo :: MonadAsync m =>
269 (Config -> Config) -> Stream m a -> Stream m a -> Stream m a
270parTwo modifier stream1 stream2 =
273 modifier (Stream.toStreamK stream1) (Stream.toStreamK stream2)
275-------------------------------------------------------------------------------
277-------------------------------------------------------------------------------
279-- | A runner function takes a queuing function @q@ and a stream, it splits the
280-- input stream, queuing the tail and using the head to generate a stream.
281-- 'mkEnqueue' takes a runner function and generates the queuing function @q@.
282-- Note that @q@ and the runner are mutually recursive, mkEnqueue ties the knot
284{-# INLINE mkEnqueue #-}
285mkEnqueue :: MonadAsync m =>
287 -> ((K.StreamK m a -> m ()) -> K.StreamK m a -> K.StreamK m b)
288 -> m (K.StreamK m a -> m ())
289mkEnqueue chan runner = do
290 runInIO <- askRunInIO
293 -- Enqueue the outer loop
294 liftIO $ enqueue chan False (runInIO, runner q stream)
295 -- XXX In case of eager dispatch we can just directly dispatch
296 -- a worker with the tail stream here rather than first queuing
297 -- and then dispatching a worker which dequeues the work. The
298 -- older implementation did a direct dispatch here and its perf
299 -- characterstics looked much better.
303-- | Takes the head element of the input stream and queues the tail of the
304-- stream to the channel, then maps the supplied function on the head and
305-- evaluates the resulting stream.
307-- This function is designed to be used by worker threads on a channel to
308-- concurrently map and evaluate a stream.
309{-# INLINE parConcatMapChanK #-}
310parConcatMapChanK :: MonadAsync m =>
311 Channel m b -> (a -> K.StreamK m b) -> K.StreamK m a -> K.StreamK m b
312parConcatMapChanK chan f stream =
313 let run q = concatMapDivK q f
314 in K.concatMapEffect (`run` stream) (mkEnqueue chan run)
315 -- K.parConcatMap (_appendWithChanK chan) f stream
317{-# INLINE parConcatMapChanKAny #-}
318parConcatMapChanKAny :: MonadAsync m =>
319 Channel m b -> (a -> K.StreamK m b) -> K.StreamK m a -> K.StreamK m b
320parConcatMapChanKAny chan f stream =
321 let done = K.nilM (stopChannel chan)
322 run q = concatMapDivK q (\x -> K.append (f x) done)
323 in K.concatMapEffect (`run` stream) (mkEnqueue chan run)
325{-# INLINE parConcatMapChanKFirst #-}
326parConcatMapChanKFirst :: MonadAsync m =>
327 Channel m b -> (a -> K.StreamK m b) -> K.StreamK m a -> K.StreamK m b
328parConcatMapChanKFirst chan f stream =
329 let done = K.nilM (stopChannel chan)
330 run q = concatMapDivK q f
331 in K.concatEffect $ do
332 res <- K.uncons stream
334 Nothing -> return K.nil
336 q <- mkEnqueue chan run
338 return $ K.append (f h) done
340{-# INLINE parConcatMapChanKGeneric #-}
341parConcatMapChanKGeneric :: MonadAsync m =>
344 -> (a -> K.StreamK m b)
347parConcatMapChanKGeneric modifier chan f stream = do
348 let cfg = modifier defaultConfig
349 case getStopWhen cfg of
350 AllStop -> parConcatMapChanK chan f stream
351 FirstStops -> parConcatMapChanKFirst chan f stream
352 AnyStops -> parConcatMapChanKAny chan f stream
354-- XXX Add a deep evaluation variant that evaluates individual elements in the
355-- generated streams in parallel.
357-- | Allocate a channel and use it to concurrently evaluate the streams
358-- generated by the mapped function.
360{-# INLINE parConcatMapK #-}
361parConcatMapK :: MonadAsync m =>
362 (Config -> Config) -> (a -> K.StreamK m b) -> K.StreamK m a -> K.StreamK m b
363parConcatMapK modifier f input =
364 let g = parConcatMapChanKGeneric modifier
365 in withChannelK modifier input (`g` f)
367-- | Map each element of the input to a stream and then concurrently evaluate
368-- and concatenate the resulting streams. Multiple streams may be evaluated
369-- concurrently but earlier streams are perferred. Output from the streams are
370-- used as they arrive.
374-- >>> parConcatMap modifier f stream = Stream.parConcat modifier $ fmap f stream
378-- >>> f cfg xs = Stream.fold Fold.toList $ Stream.parConcatMap cfg id $ Stream.fromList xs
380-- The following streams finish in 4 seconds:
382-- >>> stream1 = Stream.fromEffect (delay 4)
383-- >>> stream2 = Stream.fromEffect (delay 2)
384-- >>> stream3 = Stream.fromEffect (delay 1)
385-- >>> f id [stream1, stream2, stream3]
391-- Limiting threads to 2 schedules the third stream only after one of the first
392-- two has finished, releasing a thread:
394-- >>> f (Stream.maxThreads 2) [stream1, stream2, stream3]
398-- When used with a Single thread it behaves like serial concatMap:
400-- >>> f (Stream.maxThreads 1) [stream1, stream2, stream3]
404-- >>> stream1 = Stream.fromList [1,2,3]
405-- >>> stream2 = Stream.fromList [4,5,6]
406-- >>> f (Stream.maxThreads 1) [stream1, stream2]
409-- Schedule all streams in a round robin fashion over the available threads:
411-- >>> f cfg xs = Stream.fold Fold.toList $ Stream.parConcatMap (Stream.interleaved True . cfg) id $ Stream.fromList xs
413-- >>> stream1 = Stream.fromList [1,2,3]
414-- >>> stream2 = Stream.fromList [4,5,6]
415-- >>> f (Stream.maxThreads 1) [stream1, stream2]
418{-# INLINE parConcatMap #-}
419parConcatMap :: MonadAsync m =>
420 (Config -> Config) -> (a -> Stream m b) -> Stream m a -> Stream m b
421parConcatMap modifier f stream =
424 modifier (Stream.toStreamK . f) (Stream.toStreamK stream)
426-- | Evaluate the streams in the input stream concurrently and combine them.
428-- >>> parConcat modifier = Stream.parConcatMap modifier id
430{-# INLINE parConcat #-}
431parConcat :: MonadAsync m =>
432 (Config -> Config) -> Stream m (Stream m a) -> Stream m a
433parConcat modifier = parConcatMap modifier id
435-------------------------------------------------------------------------------
437-------------------------------------------------------------------------------
439-- | Like 'parConcat' but works on a list of streams.
441-- >>> parList modifier = Stream.parConcat modifier . Stream.fromList
443{-# INLINE parList #-}
444parList :: MonadAsync m => (Config -> Config) -> [Stream m a] -> Stream m a
445parList modifier = parConcat modifier . Stream.fromList
447-- | Like 'concat' but works on a list of streams.
449-- >>> parListLazy = Stream.parList id
451{-# INLINE parListLazy #-}
452parListLazy :: MonadAsync m => [Stream m a] -> Stream m a
453parListLazy = parList id
455-- | Like 'parListLazy' but interleaves the streams fairly instead of prioritizing
456-- the left stream. This schedules all streams in a round robin fashion over
457-- limited number of threads.
459-- >>> parListInterleaved = Stream.parList (Stream.interleaved True)
461{-# INLINE parListInterleaved #-}
462parListInterleaved :: MonadAsync m => [Stream m a] -> Stream m a
463parListInterleaved = parList (interleaved True)
465-- | Like 'parListLazy' but with 'ordered' on.
467-- >>> parListOrdered = Stream.parList (Stream.ordered True)
469{-# INLINE parListOrdered #-}
470parListOrdered :: MonadAsync m => [Stream m a] -> Stream m a
471parListOrdered = parList (ordered True)
473-- | Like 'parListLazy' but with 'eager' on.
475-- >>> parListEager = Stream.parList (Stream.eager True)
477{-# INLINE parListEager #-}
478parListEager :: MonadAsync m => [Stream m a] -> Stream m a
479parListEager = parList (eager True)
481-- | Like 'parListEager' but stops the output as soon as the first stream stops.
483-- >>> parListEagerFst = Stream.parList (Stream.eager True . Stream.stopWhen Stream.FirstStops)
485{-# INLINE parListEagerFst #-}
486parListEagerFst :: MonadAsync m => [Stream m a] -> Stream m a
487parListEagerFst = parList (eager True . stopWhen FirstStops)
489-- | Like 'parListEager' but stops the output as soon as any of the two streams
494-- >>> parListEagerMin = Stream.parList (Stream.eager True . Stream.stopWhen Stream.AnyStops)
496{-# INLINE parListEagerMin #-}
497parListEagerMin :: MonadAsync m => [Stream m a] -> Stream m a
498parListEagerMin = parList (eager True . stopWhen AnyStops)
500-------------------------------------------------------------------------------
502-------------------------------------------------------------------------------
504-- | Apply an argument stream to a function stream concurrently. Uses a
505-- shared channel for all individual applications within a stream application.
506{-# INLINE parApply #-}
507{-# SPECIALIZE parApply ::
508 (Config -> Config) -> Stream IO (a -> b) -> Stream IO a -> Stream IO b #-}
509parApply :: MonadAsync m =>
510 (Config -> Config) -> Stream m (a -> b) -> Stream m a -> Stream m b
511parApply modifier stream1 stream2 =
514 (\g -> parConcatMap modifier (Stream.fromPure . g) stream2)
517-------------------------------------------------------------------------------
519-------------------------------------------------------------------------------
524-- >>> parMapM modifier f = Stream.parConcatMap modifier (Stream.fromEffect . f)
526-- Example, the following example finishes in 1 second as all actions run in
527-- parallel. Even though results are available out of order they are ordered
528-- due to the config option::
530-- >>> f x = delay x >> return x
531-- >>> Stream.fold Fold.toList $ Stream.parMapM (Stream.ordered True) f $ Stream.fromList [3,2,1]
537{-# INLINE parMapM #-}
538parMapM :: MonadAsync m =>
539 (Config -> Config) -> (a -> m b) -> Stream m a -> Stream m b
540parMapM modifier f = parConcatMap modifier (Stream.fromEffect . f)
543-- >>> parSequence modifier = Stream.parMapM modifier id
545{-# INLINE parSequence #-}
546parSequence :: MonadAsync m =>
547 (Config -> Config) -> Stream m (m a) -> Stream m a
548parSequence modifier = parMapM modifier id
550-- | Evaluates the streams being zipped in separate threads than the consumer.
551-- The zip function is evaluated in the consumer thread.
553-- >>> parZipWithM cfg f m1 m2 = Stream.zipWithM f (Stream.parEval cfg m1) (Stream.parEval cfg m2)
555-- Multi-stream concurrency options won't apply here, see the notes in
558-- If you want to evaluate the zip function as well in a separate thread, you
559-- can use a 'parEval' on 'parZipWithM'.
561{-# INLINE parZipWithM #-}
562parZipWithM :: MonadAsync m
563 => (Config -> Config)
568parZipWithM cfg f m1 m2 = Stream.zipWithM f (parEval cfg m1) (parEval cfg m2)
571-- >>> parZipWith cfg f = Stream.parZipWithM cfg (\a b -> return $ f a b)
573-- >>> m1 = Stream.fromList [1,2,3]
574-- >>> m2 = Stream.fromList [4,5,6]
575-- >>> Stream.fold Fold.toList $ Stream.parZipWith id (,) m1 m2
576-- [(1,4),(2,5),(3,6)]
578{-# INLINE parZipWith #-}
579parZipWith :: MonadAsync m
580 => (Config -> Config)
585parZipWith cfg f = parZipWithM cfg (\a b -> return $ f a b)
587-- | Like 'mergeByM' but evaluates both the streams concurrently.
591-- >>> parMergeByM cfg f m1 m2 = Stream.mergeByM f (Stream.parEval cfg m1) (Stream.parEval cfg m2)
593{-# INLINE parMergeByM #-}
594parMergeByM :: MonadAsync m
595 => (Config -> Config)
596 -> (a -> a -> m Ordering)
600parMergeByM cfg f m1 m2 = Stream.mergeByM f (parEval cfg m1) (parEval cfg m2)
602-- | Like 'mergeBy' but evaluates both the streams concurrently.
606-- >>> parMergeBy cfg f = Stream.parMergeByM cfg (\a b -> return $ f a b)
608{-# INLINE parMergeBy #-}
609parMergeBy :: MonadAsync m
610 => (Config -> Config)
611 -> (a -> a -> Ordering)
615parMergeBy cfg f = parMergeByM cfg (\a b -> return $ f a b)
617-------------------------------------------------------------------------------
619-------------------------------------------------------------------------------
621-- | Same as 'concatIterate' but concurrent.
624{-# INLINE parConcatIterate #-}
625parConcatIterate :: MonadAsync m =>
630parConcatIterate modifier f input =
632 $ withChannelK modifier (Stream.toStreamK input) iterateStream
636 iterateStream channel stream =
637 parConcatMapChanKGeneric modifier channel (generate channel) stream
640 -- XXX The channel q should be FIFO for DFS, otherwise it is BFS
641 x `K.cons` iterateStream channel (Stream.toStreamK $ f x)
643-------------------------------------------------------------------------------
645-------------------------------------------------------------------------------
650-- >>> parRepeatM cfg = Stream.parSequence cfg . Stream.repeat
652-- Generate a stream by repeatedly executing a monadic action forever.
653{-# INLINE parRepeatM #-}
654parRepeatM :: MonadAsync m => (Config -> Config) -> m a -> Stream m a
655parRepeatM cfg = parSequence cfg . Stream.repeat
657-- | Generate a stream by concurrently performing a monadic action @n@ times.
661-- >>> parReplicateM cfg n = Stream.parSequence cfg . Stream.replicate n
663-- Example, 'parReplicateM' in the following example executes all the
664-- replicated actions concurrently, thus taking only 1 second:
666-- >>> Stream.fold Fold.drain $ Stream.parReplicateM id 10 $ delay 1
669{-# INLINE parReplicateM #-}
670parReplicateM :: MonadAsync m => (Config -> Config) -> Int -> m a -> Stream m a
671parReplicateM cfg n = parSequence cfg . Stream.replicate n
673-------------------------------------------------------------------------------
675-------------------------------------------------------------------------------
677-- Note: we can use another API with two callbacks stop and yield if we want
678-- the callback to be able to indicate end of stream.
680-- | Generates a callback and a stream pair. The callback returned is used to
681-- queue values to the stream. The stream is infinite, there is no way for the
682-- callback to indicate that it is done now.
686{-# INLINE_NORMAL newCallbackStream #-}
687newCallbackStream :: MonadAsync m => m (a -> m (), Stream m a)
688newCallbackStream = do
689 chan <- newChannel (eager True)
691 -- XXX Add our own thread-id to the SVar as we can not know the callback's
692 -- thread-id and the callback is not run in a managed worker. We need to
693 -- handle this better.
695 >>= modifyThread (workerThreads chan) (outputDoorBell chan)
701 (outputQueue chan) (outputDoorBell chan) (ChildYield a)
702 -- XXX Use fromChannelD?
703 return (callback, fromChannel chan)
705-- | Supplies a stream generating callback to a callback setter function. Each
706-- invocation of the callback results in a value being generated in the
711{-# INLINE fromCallback #-}
712fromCallback :: MonadAsync m => ((a -> m ()) -> m ()) -> Stream m a
713fromCallback setCallback = Stream.concatEffect $ do
714 (callback, stream) <- newCallbackStream
718{-# INLINE_NORMAL tapCountD #-}
722 -> (D.Stream m Int -> m b)
725tapCountD predicate fld (D.Stream step state) = D.Stream step' Nothing
728 {-# INLINE_LATE step' #-}
730 -- As long as we are using an "Int" for counts lockfree reads from
731 -- Var should work correctly on both 32-bit and 64-bit machines.
732 -- However, an Int on a 32-bit machine may overflow quickly.
733 countVar <- liftIO $ Unboxed.newIORef (0 :: Int)
736 $ Unboxed.toStreamD countVar
737 return $ Skip (Just (countVar, tid, state))
739 step' gst (Just (countVar, tid, st)) = do
744 $ liftIO $ Unboxed.modifyIORef' countVar (+ 1)
745 return $ Yield x (Just (countVar, tid, s))
746 Skip s -> return $ Skip (Just (countVar, tid, s))
748 liftIO $ killThread tid
751-- | @tapCount predicate fold stream@ taps the count of those elements in the
752-- stream that pass the @predicate@. The resulting count stream is sent to
753-- another thread which folds it using @fold@.
755-- For example, to print the count of elements processed every second:
757-- >>> rate = Stream.rollingMap2 (flip (-)) . Stream.delayPost 1
758-- >>> report = Stream.fold (Fold.drainMapM print) . rate
759-- >>> tap = Stream.tapCount (const True) report
760-- >>> go = Stream.fold Fold.drain $ tap $ Stream.enumerateFrom 0
762-- Note: This may not work correctly on 32-bit machines because of Int
767{-# INLINE tapCount #-}
771 -> (Stream m Int -> m b)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list