5

我正在为管道接口中的某些编码包装一个 C 库,但我遇到了一些需要做出的设计决策。

建立 C 库后,我们保留一个编码器上下文。有了这个,我们可以编码,或者改变一些参数(让我们调用 Haskell 接口到最后一个函数tune :: Context -> Int -> IO ())。我的问题有两个部分:

  1. 编码部分很容易包含在 . 中Pipe Foo Bar IO (),但我也想公开tune. 由于同时使用编码上下文必须受到锁保护,因此我需要在管道中的每次迭代时都使用锁,并tune使用相同的锁进行保护。但现在我觉得我在强迫用户隐藏锁。我在这里吠错树了吗?这种情况在管道生态系统中通常是如何解决的?就而言,我希望我的特定代码所属的管道始终在其自己的线程中运行,同时进行调整,但我不想将这种观点强加给任何用户。管道生态系统中的其他软件包似乎也没有强迫他们的用户喜欢。
  2. 不再使用的编码上下文需要正确地取消初始化。在管道生态系统中,如何确保IO在管道被销毁时处理这些事情(在这种情况下执行 som 操作)?

一个具体的例子是包装一个压缩库,在这种情况下,上面可以是:

  1. 压缩强度是可调的。我们设置了管道,它愉快地运行。假设必须序列化对压缩编解码器上下文的并发访问,最好如何在管道继续运行时更改压缩强度设置?
  2. 压缩库在设置时从 Haskell 堆中分配了一堆内存,当管道被拆除时,我们需要调用一些库函数来清理这些内存。

谢谢......这可能都很明显,但我对管道生态系统很陌生。

编辑:发布后阅读此内容,我很确定这是我在这里问过的最模糊的问题。啊! 对不起 ;-)

4

1 回答 1

4

关于(1),一般的解决方案是将您Pipe的类型更改为:

Pipe (Either (Context, Int) Foo) Bar IO ()

换句话说,它接受Foo输入和tune请求,并在内部进行处理。

那么让我们假设您有两个Producer对应于输入和调整请求的并发:

producer1 :: Producer Foo IO ()

producer2 :: Producer (Context, Int) IO ()

您可以使用pipes-concurrency创建一个它们都输入的缓冲区,如下所示:

example = do
    (output, input) <- spawn Unbounded
    -- input  :: Input  (Either (Context, Int) Foo)
    -- output :: Output (Either (Context, Int) Foo)

    let io1 = runEffect $ producer1 >-> Pipes.Prelude.map Right >-> toOutput output
        io2 = runEffect $ producer2 >-> Pipes.Prelude.map Left  >-> toOutput output
    as <- mapM async [io1, io2]
    runEffect (fromInput >-> yourPipe >-> someConsumer)
    mapM_ wait as

pipes-concurrency您可以通过阅读本教程了解有关该库的更多信息。

通过强制所有调整请求通过同一个单线程Pipe,您可以确保不会意外地对tune函数进行两次并发调用。

关于 (2) 有两种方法可以使用pipes. 更复杂的方法是使用pipes-safe库,它提供了一个bracket可以在 a 中使用的函数Pipe,但这对于您的目的来说可能是多余的,并且只存在于在管道的生命周期内获取和释放多个资源。一个更简单的解决方案是使用以下with成语来获取管道:

withEncoder :: (Pipe Foo Bar IO () -> IO r) -> IO r
withEncoder k = bracket acquire release $ \resource -> do
    k (createPipeFromResource resource)

然后用户会写:

withEncoder $ \yourPipe -> do
    runEffect (someProducer >-> yourPipe >-> someConsumer)

您可以选择使用该managed包,它可以稍微简化类型并更容易获取多个资源。您可以通过阅读我的这篇博文了解更多信息。

于 2014-09-06T21:32:48.893 回答