Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何使文件I/O更具事务性?_Haskell_Io - Fatal编程技术网

Haskell 如何使文件I/O更具事务性?

Haskell 如何使文件I/O更具事务性?,haskell,io,Haskell,Io,我正在用Haskell编写CGI脚本。当用户点击“提交”时,服务器上会运行一个Haskell程序,更新(即读取、处理、覆盖)状态文件。读取然后覆盖有时会导致惰性IO出现问题,因为在读取完输入之前,我们可能会生成一个较大的输出前缀。更糟糕的是,用户有时会在提交按钮上弹跳,两个进程实例同时运行,争夺同一个文件 实施的好方法是什么 transactionalUpdate :: FilePath -> (String -> String) -> IO () 函数(“更新”)从旧文件内

我正在用Haskell编写CGI脚本。当用户点击“提交”时,服务器上会运行一个Haskell程序,更新(即读取、处理、覆盖)状态文件。读取然后覆盖有时会导致惰性IO出现问题,因为在读取完输入之前,我们可能会生成一个较大的输出前缀。更糟糕的是,用户有时会在提交按钮上弹跳,两个进程实例同时运行,争夺同一个文件

实施的好方法是什么

transactionalUpdate :: FilePath -> (String -> String) -> IO ()
函数(“更新”)从旧文件内容计算新文件内容的位置?假定“更新”是严格的是不安全的,但可以假定它是完全的(对部分更新函数的健壮性是一个额外的优点)。可以同时尝试事务,但如果文件读取后已被其他人写入,则任何事务都不能更新。在竞争文件访问权限的情况下,事务可以中止。我们可以假定系统范围内唯一临时文件名的来源


我当前尝试写入临时文件,然后使用系统复制命令覆盖。这似乎可以解决懒惰IO的问题,但我觉得它在比赛中并不安全。是否有一个经过试验和测试的公式,我们可以只装在瓶子里?

这里是一个粗略的第一部分,它依赖于底层
mkdir
的原子性。它似乎符合规范要求,但我不确定它的健壮性和速度:

import Control.DeepSeq
import Control.Exception
import System.Directory
import System.IO

transactionalUpdate :: FilePath -> (String -> String) -> IO ()
transactionalUpdate file upd = bracket acquire release update
  where
    acquire = do
      let lockName = file ++ ".lock"
      createDirectory lockName
      return lockName
    release = removeDirectory
    update _ = nonTransactionalUpdate file upd

nonTransactionalUpdate :: FilePath -> (String -> String) -> IO ()
nonTransactionalUpdate file upd = do
  h <- openFile file ReadMode
  s <- upd `fmap` hGetContents h
  s `deepseq` hClose h
  h <- openFile file WriteMode
  hPutStr h s
  hClose h
然后,我用这个脚本编译并运行了一组实例:

#!/bin/bash                                                                                                     

rm foo.txt
touch foo.txt
for i in {1..50}
do
    ./SO $i &
done
当且仅当相应的编号在
foo.txt
中时,打印成功更新消息的过程;所有其他人都打印了预期的
SO:foo.txt.notveryunique:createDirectory:已存在(文件存在)


更新:您实际上不想在此处使用唯一的名称;它必须是竞争过程中的一致名称。我已经相应地更新了代码。

最惯用的unixy方法是使用flock:


通常的方法是先创建临时文件,然后重命名。它在大多数平台上都运行良好。我认为这是它的基础,但我可能也需要锁定。否则我会读A读B读A写B写。我想知道我是否可以创造性地使用写锁来避免这种情况。@pigworker,如果在读取之前重命名文件会怎么样?我认为重命名应该是文件系统级别上的原子级操作。@Rotsor这可能确实是一个获得锁的好方法。谢谢。在看到@Rotsor对原始问题的评论后,我认为重命名原始文件也可以;不管是哪种方式,系统调用都应该保证原子性。目前我认为这是将文件重命名为文件输入,将文件处理为文件输出(懒散地,不用担心!),将文件重命名为文件输出。无论如何,使用原子文件系统操作获取和释放锁是一个很好的方向。也谢谢你对测试的指导。不用担心;我同意这是一个更简单的解决方案,而且对于像我最初错误地建议一个唯一后缀这样的问题,它更加健壮。你应该可以把它放到我上面写的
acquire
release
定义中。听起来像是羊群是一条路要走,但是谢谢你的帮助。哦,已经有人装了吗?看来这就解决了锁定问题,只剩下追尾回避问题了。也就是说,使用这种锁定机制不利于对后者使用基于重命名的方法。我认为对后者使用重命名方法是可以的。羊群是建议性的选择锁。抓取“file.ext”上的锁。然后写入“file.ext.out”,然后重命名为“file.ext”,最后释放锁?是的,只要我确保在锁定主文件的同时没有竞争临时文件名,就可以了。谢谢
#!/bin/bash                                                                                                     

rm foo.txt
touch foo.txt
for i in {1..50}
do
    ./SO $i &
done