我正在为 C 中的库创建一个 FFI 模块,该库希望在调用其他任何东西之前调用 1 次不可重入函数。这个调用是幂等的,但是是有状态的,所以我可以在每个 Haskell 调用中调用它。但它很慢,并且由于不可重入,它可能会导致冲突。
那么现在是使用 unsafePerformIO 的合适时机吗?我可以将 Bool 包装在不安全的 IORef 或 MVar 中,通过忽略后续调用(全局隐藏 IORef 状态为 False 的调用)来使这些初始化调用具有幂等性。
如果不是,那么正确的方法是什么?
我正在为 C 中的库创建一个 FFI 模块,该库希望在调用其他任何东西之前调用 1 次不可重入函数。这个调用是幂等的,但是是有状态的,所以我可以在每个 Haskell 调用中调用它。但它很慢,并且由于不可重入,它可能会导致冲突。
那么现在是使用 unsafePerformIO 的合适时机吗?我可以将 Bool 包装在不安全的 IORef 或 MVar 中,通过忽略后续调用(全局隐藏 IORef 状态为 False 的调用)来使这些初始化调用具有幂等性。
如果不是,那么正确的方法是什么?
我更喜欢初始化一次并提供不可伪造令牌作为您已初始化机器的证据的方法。
所以你的证据是:
data Token = Token
您抽象地导出。
然后你的初始化函数可以返回这个证据。
init :: IO Token
现在,您需要将该证明传递给您的 API:
bar :: Token -> IO Int
bar !tok = c_call_bar
等等
您现在可以用 monad 或一些更高阶的初始化环境来包装这些东西以使其更干净,但这是基本思想。
使用隐藏状态初始化 C 库的问题是,您最终要么无法并行访问库,要么在 GHCi 中遇到问题,混合编译和字节码,加载了两个不同版本的 C 库(这将失败链接器错误)。
我想指出,目前建议/而不是withSocketsDo
由 Neil Mitchell提出一些新技巧,基于evaluate
(“当执行结果 IO 操作时,强制其参数被评估为弱头部正常形式。”):
withSocketsDo act = do evaluate withSocketsInit; act
{-# NOINLINE withSocketsInit #-}
withSocketsInit = unsafePerformIO $ do
initWinsock
termWinsock
我消除调用 withSocketsDo 要求的方法是让它变得非常便宜,然后将它洒在可能需要的任何地方。
不一定这是一个美丽的主意...
(另请参阅他在库中宣布此更新的答案。)