我想写一个函数
forkos_try :: IO (Maybe α) -> IO (Maybe α)
它接受命令x
。 x 是一个命令式操作,它首先改变状态,然后检查该状态是否困惑。 (它不会执行任何外部操作,这需要某种操作系统级沙箱来恢复状态。)
- 如果
x
计算结果为Just y
,forkos_try
返回Just y
。 - 否则,
forkos_try
回滚状态,并返回Nothing
。
在内部,它应该fork()
到线程parent
和child
中,x
在上运行> child
。
- 如果
x
成功,child
应继续运行(返回x
的结果),而parent
应终止 - 否则,
parent
应继续运行(返回Nothing
),而child
应死亡
问题:如何编写具有与 forkos_try
等效或更强大语义的内容? 注意-- 状态突变(由x
)位于外部库中,并且无法在线程之间传递。因此,保持哪个线程事件的语义很重要。
正式地,“继续运行”意味着“执行一些延续rest::Maybe α -> IO ()
”。但是,该延续并未在代码中明确保留。
对于我的情况,我认为(暂时)可以使用 forkOS
以不同的风格编写它(这需要运行整个计算 child
) ,因为我可以为 rest
编写一个显式表达式。但是,令我困扰的是,我无法弄清楚如何使用原始函数 forkOS
来做到这一点——人们会认为它足够通用以支持任何特定情况(这可能会表现为一种高-级别 API,如 forkos_try
)。
编辑 - 如果问题仍然不清楚,请参阅带有显式 rest
的示例代码 [ http://pastebin.com/nJ1NNdda ].
附:我已经有一段时间没有写并发代码了;希望我对 POSIX fork()
的了解是正确的!提前致谢。
最佳答案
如果你明确地对状态进行建模,事情就会变得更容易推理。
someStateFunc :: (s -> Maybe (a, s))
-- inside some other function
case someStateFunc initialState of
Nothing -> ... -- it failed. stick with initial state
Just (a, newState) -> ... -- it suceeded. do something with
-- the result and new state
对于不可变状态,“回滚”很简单:只需继续使用 initialState
即可。而且“不回滚”也很简单:只需使用 newState
即可。
所以...我从你的解释中假设这个“外部库”执行一些不平凡的 IO 效果,但这些效果仅限于一些已知且可逆的操作(修改文件、IORef 等)。有些事情是无法逆转的(发射导弹、写入标准输出等),所以我在这里看到两个选择之一:
- 克隆世界,并在沙箱中运行 Action 。如果成功,则继续在现实世界中运行该操作。
- 克隆世界,并在现实世界中运行 Action 。如果失败,则用您之前拍摄的快照替换现实世界。
当然,这两者实际上都是同一个做法:fork the world。一个世界负责行动,另一个世界不负责。如果行动成功,那么那个世界就会继续;否则,另一个世界仍将继续。您建议通过构建 forkOS
来实现此目的,这将克隆程序的整个状态,但这不足以处理例如文件修改等问题。请允许我建议一种更接近简单的不可变状态的方法:
tryIO :: IO s -> (s -> IO ()) -> IO (Maybe a) -> IO (Maybe a)
tryIO save restore action = do
initialState <- save
result <- action
case result of
Nothing -> restore initialState >> return Nothing
Just x -> return (Just x)
这里您必须提供一些数据结构s
,以及一种从所述数据结构保存
和恢复
的方法。这使您可以灵活地执行您认为必要的任何克隆。 (例如,save
可以将某个文件复制到临时位置,然后restore
可以将其复制回来并删除临时文件。或者save
可以复制某些 IORef 的值,然后 restore
可以将值恢复。)这种方法可能不是最有效的,但非常简单。
关于haskell - 如何在 Haskell 中实现 fork try-catch?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9497195/