2014-12-26 23 views
3

我想了解協程程序,但不太瞭解它們的目的,因爲使用forkIO時存在線程。什麼用例確實需要在線程上使用協程?Haskell中的fork和協程程序

+3

如果你說你正在討論的協程,它會有幫助,因爲它們不是標準庫的一部分。另一方面,它們作爲一個圖書館來實現是微不足道的,所以它們可能在多個地方可用。 – Carl

+0

假設你使用像'forkIO'的'BoundedChan'這樣的通信機制也可能是值得的。 – rightfold

回答

5

從你的問題來看,如果你正在談論一個特定的Haskell協同實現(如果是的話,請添加一個鏈接)或關於一般概念,有點不清楚。

使用forkIO和某種線程間通信是如何實現協程的一種方式。其優點是,這種方式可以採取具有的利用多個CPU /核心,但在我看來,有幾個缺點:

  • 顯式併發IO爲主,因此所有的計算必須在IO單子運行。
  • 你必須明確地實現線程間通信。
  • 你必須照顧開始線程,更重要的是,處理它們並防止飢餓/死鎖。
  • 該體系結構(顯然)是多線程的。在某些情況下,這可能是一個缺點。例如,您可能希望您的計算是純粹的,確定性的,單線程的,但仍使用協程的概念。

我會進一步假設你的問題是關於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 

現在讓我們說,我們認識到,每一個週期後產生效率太低。相反,我們希望調用者在再次暫停之​​前決定我們應該計算多少個週期。爲了實現這個目標,我們只是Request更換Yield函子:

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,階乘的計算是純潔的,他們allowe任何單子(包括Identity)。

儘管如此,我們使用Haskell's parallelism與報告代碼並行運行純計算(在從協同程序產生之前,我們創建了一個使用par計算乘法步驟的火花)。

也許最重要的是,這些類型可以確保協程不會不當。協程不可能發生死鎖 - 產生或請求反饋總是與適當的響應耦合在一起(除非調用者決定不繼續協程,在這種情況下,它會被垃圾收集器自動刪除,沒有阻塞的線程) 。

4

沒有用例真正地確定了協程。您可以使用協程來做所有事情,您可以使用forkIO +某些通信通道。事實上,我相信Go(一種併發性非常便宜的語言,就像Haskell一樣)完全避開協程,並且使用併發輕量級線程(「goroutines」)完成所有任務。

但是,有時forkIO矯枉過正。有時候你並不需要需要併發性,你只想將一個問題分解成概念上獨立的指令流,這些指令流在某些明確確定的點上相互屈服。

考慮從文件讀取並寫入另一個文件的任務。而不是有一個單一的嵌套循環,一個更可重用的解決方案是組成一個文件讀取協程和文件寫入協議。當您稍後決定將文件打印到屏幕上時,您根本不需要修改文件讀取協程,只需對其進行不同的組合。但請注意,這個問題與併發性無關,它關係到關注點和可重用性的分離。