2014-12-26 3 views
3

Я пытаюсь понять Corouts, но не совсем понимаю их цель, учитывая существование потоков с forkIO. Какие варианты использования требуют использования сопрограмм по потокам?forkIO и сопрограммы в Haskell

+3

Это помогло бы, если бы вы сказали, какие сопрограммы вы говорите, поскольку они не являются частью стандартных библиотек. С другой стороны, они тривиальны для реализации в качестве библиотеки, поэтому они, вероятно, доступны в нескольких местах. – Carl

+0

Возможно, также стоит предположить, что вы используете какой-то механизм связи, например 'BoundedChan' с' forkIO'. – rightfold

ответ

5

Это немного непонятно из вашего вопроса, если вы говорите о реализации конкретной версии Haskell coroutines (если да, пожалуйста, добавьте ссылку) или об общей концепции.

Использование forkIO и некоторая межпоточная связь - это один из способов реализации сопрограмм. Преимущество заключается в том, что таким образом вы можете воспользоваться преимуществами, имеющих несколько процессоров/ядер, но, на мой взгляд, есть несколько недостатков:

  • Явный параллелизм 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 

Теперь предположим, что мы понимаем, что приносит после каждого цикла слишком неэффективно. Вместо этого мы хотим, чтобы вызывающий абонент определил, сколько циклов мы должны вычислить, прежде чем снова приостанавливать.Для достижения этой цели, мы просто заменить 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 основы, вычисление факториалов являются чистым, они allowe любой монады (включая Identity).

По-прежнему, используя Haskell's parallelism, мы проводили чистые вычисления параллельно с кодом отчетности (перед тем как приступить к сопрограмме, мы создаем искру, которая вычисляет шаг умножения, используя par).

И, возможно, самое главное, типы гарантируют, что сопрограммы не могут плохо себя вести. Невозможно, как сопроцессоры могут затормозить - получение или запрос обратной связи всегда сочетается с соответствующим ответом (если вызывающий не решает не продолжать сопрограмму, и в этом случае она автоматически удаляется сборщиком мусора, нет заблокированной нити) ,

4

Никаких прецедентов не требуется. Все, что вы можете сделать с сопрограммами, вы можете сделать с помощью forkIO + канала связи. На самом деле, я считаю, что Go (язык, на котором параллелизм очень дешев, например, в Haskell) вообще избегает сопрограмм и делает все с помощью параллельных легких потоков («goroutines»).

Однако иногда forkIO является overkill. Иногда вам не нужен , необходимо только разложить проблему на концептуально отдельные потоки команд, которые уступают друг другу в определенных явно определенных точках.

Рассмотрите задачу чтения из файла и записи в другой. Вместо того, чтобы иметь монолитный вложенный цикл, более многоразовым решением было бы составить сопроводительную копию файла с записью файла. Когда вы позже решите распечатать файл на экране, вам не нужно вообще изменять сопроводительную программу для чтения файлов, вам нужно только составить ее по-другому. Но обратите внимание, что эта проблема не имеет ничего общего с параллелизмом, это касается разделения проблем и повторного использования.

Смежные вопросы