以线程安全的方式创建文件

时间:2013-01-24 20:12:30

标签: php thread-safety locking file-locking

我有一个文件名数组,每个进程只需要创建和写入一个文件。

这就是我的意思:

foreach ($filenames as $VMidFile) {
    if (file_exists($VMidFile)) { // A
        continue;
    }

    $fp = fopen($VMidFile, 'c'); // B

    if (!flock($fp, LOCK_EX | LOCK_NB)) { // C
        continue;
    }

    if (!filesize($VMidFile)) { // D
        // write to the file;

        flock($fp, LOCK_UN);
        fclose($fp);
        break;
    }

    flock($fp, LOCK_UN);
    fclose($fp); // E
}

但我不喜欢我依赖filesize

有任何建议以另一种(更好的)方式进行吗?

UPD :添加标签以便轻松讨论

UPD 2 :我正在使用filesize,因为我没有看到任何其他可靠方法来检查当前线程是否创建了文件(因此它还是空的)

UPD 3 :解决方案应该条件无竞争。

4 个答案:

答案 0 :(得分:3)

一个可能的,稍微丑陋的解决方案是锁定一个锁文件,然后测试该文件是否存在:

$lock = fopen("/tmp/".$filename."LOCK", "w"); // A

if (!flock($lock, LOCK_EX)) { // B
    continue;
}
if(!file_exists($filename)){ // C
    //File doesn't exist so we know that this thread will create it
    //Do stuff to $filename
    flock($lock, LOCK_UN); // D
    fclose($lock);
}else{
    //File exists. This thread didn't create it (at least in this iteration).
    flock($lock, LOCK_UN);
    fclose($lock);
}

这应允许对文件进行独占访问,并允许决定对fopen($VMidFile, 'c');的调用是否会创建文件。

答案 1 :(得分:2)

而不是创建一个文件并希望它不会受到干扰:

  1. 创建临时文件
  2. 对其执行所有必要的文件操作
  3. 如果位置不存在,则
  4. rename到新位置。
  5. 从技术上讲,由于rename将覆盖目标,因此并发线程仍有可能发生冲突。如果你有这样的话,那是非常不可能的:

    if(!file_exists($lcoation) { rename(...
    

    您可以使用md5_file在此阻止后验证文件内容是否正确。

答案 2 :(得分:1)

您可以使用semaphores保护独占访问权限(仅限UNIX,并且只要安装了sysvsem扩展名):

$s = sem_get(ftok($filename), 'foo');
sem_acquire($s);

// Do some critical work...

sem_release($s);

否则您也可以使用flock。它不需要任何特殊扩展,但根据comments on PHP.net比使用信号量慢一点:

$a = fopen($file, 'w');
flock($a, LOCK_EX);

// Critical stuff, again

flock($a, LOCK_UN);

答案 3 :(得分:0)

在fopen调用中使用模式'x'而不是'c'。并检查生成的$ fp,如果为false,则该文件不是由当前线程创建的,您应该继续使用下一个文件名。

此外,根据PHP的安装设置,如果fopen($ VMidFile,'x')无法创建文件,则可能需要在fopen调用前放置@以禁止任何警告,因为它已经存在。< / p>

即使没有鸡群,这也应该有用。