如何用Haskell模拟Go的频道?

时间:2010-12-23 20:52:52

标签: haskell go channel goroutine

我最近开始阅读Go编程语言,我发现通道变量是一个非常吸引人的概念。是否可以在Haskell中模拟相同的概念?也许有一个数据类型Channel a和一个monad结构来启用可变状态和函数,就像关键字go一样。

我在并发编程方面不是很好,Haskell中这样的简单通道传递机制会让我的生活变得更轻松。

修改

人们让我澄清我对转换为Haskell感兴趣的Go模式。所以Go的通道变量是第一类的,可以传递并由函数返回。我可以读取和写入这些通道,因此可以在可以并发运行的例程之间轻松进行通信。 Go还有一个go关键字,根据语言规范启动并发执行函数作为独立线程,并继续执行代码而无需等待。

我感兴趣的确切模式是这样的(Go的语法很奇怪 - 变量是由varName varType声明的,而不是通常的反转方式 - 但我认为它是可读的):

func generateStep(ch chan int) {
      //ch is a variable of type chan int, which is a channel that comunicate integers
      for {
          ch <- randomInteger() //just sends random integers in the channel 
      }

func filter(input, output chan int) {
      state int
      for {
          step <- input  //reads an int from the input channel
          newstate := update(state, step) //update the variable with some update function
          if criteria(newstate, state) {
             state = newstate // if the newstate pass some criteria, accept the update
          } 
          output <- state    //pass it to the output channel
      } 
}

func main() {
    intChan := make(chan int) 
    mcChan  := make(chan int) 
    go generateStep(intChan)     // execute the channels concurrently
    go filter(intChan, mcChan)
    for i:=0; i<numSteps; i++  {
        x <- mcChan        // get values from the filtered channel
        accumulateStats(x)  // calculate some statistics
    } 
    printStatisticsAbout(x)
}

我的主要兴趣是进行蒙特卡罗模拟,其中我通过尝试修改系统的当前状态并接受修改(如果它满足某些标准)来顺序生成配置。

使用那些通道的东西,我可以编写一个非常简单,可读和小的蒙特卡罗模拟,在我的多核处理器中并行运行真的给我留下了深刻的印象。

问题在于Go有一些局限性(特别是它缺乏我在Haskell习惯的方式中的多态性),除此之外,我真的很喜欢Haskell并且不想将它交易掉。所以问题是,是否有一些方法可以使用一些看起来像上面代码的机制来轻松地在Haskell中进行并发模拟。

编辑(2,上下文): 我不是在计算机科学中学习,特别是在并发方面。我只是一个创建简单程序来解决日常研究中的简单问题的人,这个程序与CS完全没有关系。我只是发现Haskell有趣的方式,并喜欢用它做我的小家务。

我从未听说过单独的pi演算或CSP频道。对不起,如果这个问题看起来很糟糕,那可能是我对这个问题的无知。

你是对的,我应该更具体地说一下Go中我想要在Haskell中复制什么模式,我会尝试编辑更具体的问题。但不要指望深刻的理论问题。事情就是这样,从我阅读和编码的一些东西,似乎Go有一个简洁的方式来做并发(在我的情况下,这只意味着我的工作,使我的所有核心数字计算更容易嗡嗡声),并且如果我可以在Haskell中使用类似的语法,我会很高兴。

2 个答案:

答案 0 :(得分:32)

我认为您所寻找的是来自Base的Control.Concurrent.Chan。我没有发现它与go的chans有任何不同,除了明显的haskellifications。频道不是特别的东西,请看wiki page about it

频道是一个更通用的概念communicating sequential processes (CSP)的一部分,如果你想在Haskell中以CSP的风格进行编程,你可能需要看一下Communicating Haskell Processes (CHP)包。

CHP只是在Haskell中进行并发的一种方式,请查看Haskellwiki concurrency page以获取更多信息。我认为您的用例可能最好使用Data Parrallel Haskell编写,但这目前正在进行中,因此您可能希望暂时使用其他内容。

答案 1 :(得分:1)

扩展HaskellElephant的答案,Control.Concurrent.Chan是通道和Control.Concurrent的forkIO可以模拟go关键字的方式。为了使语法与Go更相似,可以使用这组别名:

import Control.Concurrent (forkIO)
import Control.Concurrent.Chan (newChan, readChan, writeChan)
import Control.Concurrent.MVar (newMVar, swapMVar, readMVar)

data GoChan a = GoChan { chan :: Chan a, closed :: MVar Bool }

go :: IO () -> IO ThreadId
go = forkIO

make :: IO (GoChan a)
make = do
    ch <- newChan
    cl <- newMVar False
    return $ GoChan ch cl

get :: GoChan a -> IO a
get ch = do
    cl <- readMVar $ closed ch
    if cl
        then error "Can't read from closed channel!"
        else readChan $ chan ch

(=->) :: a -> GoChan a -> IO ()
v =-> ch = do
    cl <- readMVar $ closed ch
    if cl
        then error "Can't write to closed channel!"
        else writeChan (chan ch) v

forRange :: GoChan a -> (a -> IO b) -> IO [b]
forRange ch func = fmap reverse $ range_ ch func []
    where range_ ch func acc = do
        cl <- readMVar $ closed ch
        if cl
            then return ()
            else do
                v <- get ch
                func v
                range_ ch func $ v : acc
close :: GoChan a -> IO ()
close ch = do
    swapMVar (closed ch) True
    return ()

这可以这样使用:

import Control.Monad

generate :: GoChan Int -> IO ()
generate c = do
    forM [1..100] (=-> c)
    close c

process :: GoChan Int -> IO ()
process c = forRange c print

main :: IO ()
main = do
    c <- make
    go $ generate c
    process c

(警告:未经测试的代码)