Haskell中的forkIO和协同程序

use*_*220 5 concurrency multithreading haskell coroutine

我试图理解Coroutines但是由于forkIO存在线程,所以我不太了解它们的用途.什么用例确实需要在线程上使用协同程序?

Pet*_*lák 7

如果您正在讨论特定的Haskell协程实现(如果是,请添加链接)或关于一般概念,那么您的问题有点不清楚.

使用forkIO和某种线程间通信是实现协同程序的一种方法.优点是,这种方式可以利用多个CPU /核心,但在我看来,有几个缺点:

  • IO基于显式并发,因此所有计算都必须在IOmonad中运行.
  • 您必须显式实现线程间通信.
  • 您必须处理启动线程,更重要的是处理它们以及防止饥饿/死锁.
  • 该架构(显然)是多线程的.在某些情况下,这可能是一个缺点.例如,您可能希望计算是纯粹的,确定性的,单线程的,但仍然使用协同程序的概念.

我进一步假设你的问题是关于这个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
Run Code Online (Sandbox Code Playgroud)

现在让我们说我们意识到在每个周期之后屈服效率太低.相反,我们希望调用者在再次暂停之​​前决定我们应该计算多少个周期.为此,我们只需用以下代码替换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 Code Online (Sandbox Code Playgroud)

虽然我们的run...例子是IO基于的,但是阶乘的计算是纯粹的,它们允许任何单子(包括Identity).

仍然,使用Haskell的并行性,我们与报告代码并行运行纯计算(在从协程生成之前,我们创建一个计算乘法步骤的火花par).

也许最重要的是,这些类型确保协程不会行为不端.协同程序无法解决死锁问题 - 产生或请求反馈总是与适当的响应相结合(除非调用者决定不继续使用协程,在这种情况下它会被垃圾收集器自动删除,没有阻塞的线程) .