我正在尝试理解Coroutines但是由于forkIO存在线程,所以我不太了解它们的用途。什么用例确实需要在线程上使用协同程序?
答案 0 :(得分:7)
如果您正在讨论特定的Haskell协程实现(如果是,请添加链接)或关于一般概念,那么您的问题有点不清楚。
使用forkIO
和某种线程间通信是实现协同程序的一种方法。优点是这样你可以利用多个CPU /核心,但在我看来,有几个缺点:
IO
,因此所有计算都必须在IO
monad中运行。我会进一步假设你的问题是this Coroutine
实施。
让我举一个小例子。假设我们想要计算大因子,但由于计算可能需要很长时间,我们希望它在每个周期后暂停,以便我们可以向用户提供一些反馈。此外,我们想要发出剩余计算周期的信号:
import Control.Monad
import Control.Monad.Coroutine
import Control.Monad.Coroutine.SuspensionFunctors
import Control.Parallel
import Data.Functor.Identity
-- A helper function, a monadic version of 'pogoStick':
-- | Runs a suspendable 'Coroutine' to its completion.
pogoStickM :: Monad m => (s (Coroutine s m x) -> m (Coroutine s m x))
-> Coroutine s m x -> m x
pogoStickM spring c = resume c >>= either (pogoStickM spring <=< spring) return
factorial1 :: (Monad m) => Integer -> Coroutine (Yield Integer) m Integer
factorial1 = loop 1
where
loop r 0 = return r
loop r n = do
let r' = r * n
r' `par` yield n
(r' `pseq` loop r') (n - 1)
run1 :: IO ()
run1 = pogoStickM (\(Yield i c) -> print i >> return c) (factorial1 20) >>= print
现在让我们说我们意识到在每个周期之后屈服效率太低。相反,我们希望调用者在再次暂停之前决定我们应该计算多少个周期。为实现这一目标,我们只需将Yield
仿函数替换为Request
:
factorial2 :: (Monad m) => Integer
-> Coroutine (Request Integer Integer) m Integer
factorial2 n = loop 1 n n
where
loop r t 0 = return r
loop r t n | t >= n = r' `par` request n >>= rec
| otherwise = rec t
where
rec t' = (r' `pseq` loop r') t' (n - 1)
r' = r * n
run2 :: IO ()
run2 = pogoStickM (\(Request i c) -> print i >> return (c (i - 5)))
(factorial2 30)
>>= print
虽然我们的run...
示例基于IO
,但是阶乘的计算是纯粹的,它们允许任何monad(包括Identity
)。
仍然,使用Haskell's parallelism我们与报告代码并行运行纯计算(在从协程生成之前,我们创建了一个使用par
来计算乘法步骤的火花。)
也许最重要的是,这些类型可以确保协程不会出现行为异常。协同程序无法解决死锁问题 - 产生或请求反馈总是与适当的响应相结合(除非调用者决定不继续使用协程,在这种情况下它会被垃圾收集器自动删除,没有阻塞的线程)
答案 1 :(得分:4)
没有用例真正地证实了协同程序。您可以使用协同程序执行的所有操作都可以使用forkIO
+某些通信渠道。事实上,我相信Go(一种并发非常便宜的语言,比如在Haskell中)完全避开协同程序,并使用并发轻量级线程(“goroutines”)完成所有操作。
但是,有时forkIO
矫枉过正。有时你不需要并发,你只想将一个问题分解为概念上独立的指令流,这些指令流在某些明确确定的点上相互屈服。
考虑从文件中读取并写入另一个文件的任务。而不是拥有单片嵌套循环,更可重用的解决方案是用文件写入文件读取协程组成。当您稍后决定将文件打印到屏幕时,您根本不需要修改文件读取协程,您只需要以不同方式编写它。但请注意,这个问题与并发性无关,它与关注点的分离和可重用性有关。