我想了解協程程序,但不太瞭解它們的目的,因爲使用forkIO時存在線程。什麼用例確實需要在線程上使用協程?Haskell中的fork和協程程序
回答
從你的問題來看,如果你正在談論一個特定的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
計算乘法步驟的火花)。
也許最重要的是,這些類型可以確保協程不會不當。協程不可能發生死鎖 - 產生或請求反饋總是與適當的響應耦合在一起(除非調用者決定不繼續協程,在這種情況下,它會被垃圾收集器自動刪除,沒有阻塞的線程) 。
沒有用例真正地確定了協程。您可以使用協程來做所有事情,您可以使用forkIO
+某些通信通道。事實上,我相信Go(一種併發性非常便宜的語言,就像Haskell一樣)完全避開協程,並且使用併發輕量級線程(「goroutines」)完成所有任務。
但是,有時forkIO
是矯枉過正。有時候你並不需要需要併發性,你只想將一個問題分解成概念上獨立的指令流,這些指令流在某些明確確定的點上相互屈服。
考慮從文件讀取並寫入另一個文件的任務。而不是有一個單一的嵌套循環,一個更可重用的解決方案是組成一個文件讀取協程和文件寫入協議。當您稍後決定將文件打印到屏幕上時,您根本不需要修改文件讀取協程,只需對其進行不同的組合。但請注意,這個問題與併發性無關,它關係到關注點和可重用性的分離。
- 1. Shell程序中的fork()和wait()
- 2. C程序中的fork()
- 3. UNIX進程:fork()和wait()的
- 4. 在我的程序中的「fork part」
- 5. haskell中的素數程序
- 6. haskell程序中的錯誤
- 7. c程序中的多個fork()
- 8. XWindow應用程序中的fork()/ exec()
- 9. fork和現有線程?
- 10. C++進程fork和sigalarm
- 11. C-fork()和進程行爲
- 12. fork()子進程和父進程
- 13. 在fork/execs程序中使用Devel :: NYTProf
- 14. 優化Haskell程序
- 15. Haskell OpenGL程序代
- 16. Haskell密碼程序
- 17. Haskell程序覆蓋
- 18. Haskell程序修復
- 19. fork()的C編程
- 20. C進程fork()
- 21. python線程/ fork?
- 22. Luabind和協程
- 23. fork系統調用的應用程序
- 24. Haskell中的Android應用程序
- 25. haskell程序中的無限循環
- 26. Haskell程序中的GLUT錯誤
- 27. Docker中的靜態鏈接Haskell程序
- 28. 如何獲得以fork和execv開頭的程序的pid
- 29. 跟蹤命令程序中的Haskell程序變量
- 30. Perl fork fork線程捕獲輸出
如果你說你正在討論的協程,它會有幫助,因爲它們不是標準庫的一部分。另一方面,它們作爲一個圖書館來實現是微不足道的,所以它們可能在多個地方可用。 – Carl
假設你使用像'forkIO'的'BoundedChan'這樣的通信機制也可能是值得的。 – rightfold