7

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

这就是我的目的:

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

4 回答 4

4

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

$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');将创建文件。

于 2013-01-24T21:25:13.323 回答
2

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

  1. 创建一个临时文件
  2. 对其进行所有必要的文件操作
  3. rename如果位置不存在,则将其移至新位置。

从技术上讲,由于rename将覆盖目标,并发线程仍有可能发生冲突。如果您有以下情况,那是不太可能的:

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

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

于 2013-01-24T20:42:05.433 回答
1

您可以使用信号量来保护独占访问(仅限 UNIX,并提供sysvsem已安装的扩展):

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

// Do some critical work...

sem_release($s);

否则你也可以使用flock. 它不需要任何特殊的扩展,但根据PHP.net 上的评论,它比使用信号量要慢一些:

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

// Critical stuff, again

flock($a, LOCK_UN);
于 2013-01-24T21:03:30.550 回答
0

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

此外,根据您的 PHP 安装设置,如果 fopen($VMidFile, 'x') 因为文件已经存在而无法创建该文件,您可能希望在 fopen 调用前面放置一个 @ 以抑制任何警告。

即使没有羊群,这也应该有效。

于 2014-02-06T10:19:08.483 回答