首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >递归IO函数中的内存泄漏

递归IO函数中的内存泄漏
EN

Stack Overflow用户
提问于 2016-12-23 18:57:19
回答 3查看 1.1K关注 0票数 17

我编写了一个名为amqp-工人的库,它提供了一个名为worker的函数,用于轮询消息队列(如RabbitMQ)中的消息,并在找到消息时调用处理程序。然后返回到投票。

它漏掉了记忆。我分析过它,图表上说PAP (部分函数应用程序)是罪魁祸首。我的代码中的漏洞在哪里?如何避免在IO forever**?**和中循环时发生泄漏

以下是一些相关的功能。完整的来源在这里

示例程序。这漏了

代码语言:javascript
复制
main :: IO ()
main = do
  -- connect
  conn <- Worker.connect (fromURI "amqp://guest:guest@localhost:5672")

  -- initialize the queues
  Worker.initQueue conn queue
  Worker.initQueue conn results

  -- publish a message
  Worker.publish conn queue (TestMessage "hello world")

  -- create a worker, the program loops here
  Worker.worker def conn queue onError (onMessage conn)

工人

代码语言:javascript
复制
worker :: (FromJSON a, MonadBaseControl IO m, MonadCatch m) => WorkerOptions -> Connection -> Queue key a -> (WorkerException SomeException -> m ()) -> (Message a -> m ()) -> m ()
worker opts conn queue onError action =
  forever $ do
    eres <- consumeNext (pollDelay opts) conn queue
    case eres of
      Error (ParseError reason bd) ->
        onError (MessageParseError bd reason)

      Parsed msg ->
        catch
          (action msg)
          (onError . OtherException (body msg))
    liftBase $ threadDelay (loopDelay opts)

consumeNext

代码语言:javascript
复制
consumeNext :: (FromJSON msg, MonadBaseControl IO m) => Microseconds -> Connection -> Queue key msg -> m (ConsumeResult msg)
consumeNext pd conn queue =
    poll pd $ consume conn queue

民意测验

代码语言:javascript
复制
poll :: (MonadBaseControl IO m) => Int -> m (Maybe a) -> m a
poll us action = do
    ma <- action
    case ma of
      Just a -> return a
      Nothing -> do
        liftBase $ threadDelay us
        poll us action
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2017-01-07 13:19:28

下面是一个非常简单的示例,演示了您的问题:

代码语言:javascript
复制
main :: IO ()
main = worker

{-# NOINLINE worker #-}
worker :: (Monad m) => m ()
worker =
  let loop = poll >> loop
  in loop

poll :: (Monad m) => m a
poll = return () >> poll

如果您删除NOINLINE,或者将m专门化为IO (同时使用-O进行编译),泄漏就会消失。

我编写了一个详细的博客帖子,说明为什么这段代码会泄漏内存。快速总结是,正如里德在他的回答中指出的,代码创建并记住了>>的部分应用程序链。

我还提交了一份关于这个的ghc票

票数 16
EN

Stack Overflow用户

发布于 2017-01-07 15:17:46

也许一个更容易理解的例子就是这个

代码语言:javascript
复制
main :: IO ()
main = let c = count 0
       in c >> c

{-# NOINLINE count #-}
count :: Monad m => Int -> m ()
count 1000000 = return ()
count n = return () >> count (n+1)

为IO操作计算f >> g会产生某种类型的闭包,它同时引用了fg (基本上是fg作为状态标记上的函数的组合)。count 0返回一个thunk c,它将计算为表单return () >> return () >> return () >> ...的一个大的闭包结构。当我们执行c时,我们构建了这个结构,因为我们必须第二次执行c,所以整个结构仍然处于活动状态。因此,这个程序泄漏内存(不管是否有优化标志)。

count专门用于IO并启用优化时,GHC可以使用各种各样的技巧来避免构建这种数据结构;但是它们都依赖于知道monad是IO

回到原来的count :: Monad m => Int -> m (),我们可以通过将最后一行更改为

代码语言:javascript
复制
count n = return () >>= (\_ -> count (n+1))

现在递归调用隐藏在lambda中,因此c只是一个小的结构return () >>= (\_ -> BODY)。这实际上避免了在没有优化的情况下进行编译时的空间泄漏。但是,当启用优化时,GHC会将count (n+1)从lambda的主体中浮出(因为它不依赖于参数)

代码语言:javascript
复制
count n = return () >>= (let body = count (n+1) in \_ -> body)

现在c又成了一个大结构.

票数 4
EN

Stack Overflow用户

发布于 2017-01-03 18:52:30

内存泄漏发生在poll中。使用monad-loops,我将定义更改为以下内容:看起来untilJust执行与递归相同的操作,但修复了泄漏。

有谁能评论一下我以前对poll的定义为什么会漏掉内存?

代码语言:javascript
复制
{-# LANGUAGE FlexibleContexts #-}

module Network.AMQP.Worker.Poll where

import Control.Concurrent (threadDelay)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Base (liftBase)
import Control.Monad.Loops (untilJust)

poll :: (MonadBaseControl IO m) => Int -> m (Maybe a) -> m a
poll us action = untilJust $ do
    ma <- action
    case ma of
      Just a -> return $ Just a
      Nothing -> do
        liftBase $ threadDelay us
        return Nothing
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41306593

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档