Создать файл потокобезопасным способом
У меня есть массив имен файлов, и каждый процесс должен создавать и записывать только в один файл.
Вот к чему я пришел:
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 ответа
Возможное, немного уродливое решение - заблокировать файл блокировки и затем проверить, существует ли файл:
$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');
создаст файл.
Вместо того, чтобы создавать файл и надеяться, что он не мешает:
- создать временный файл
- сделать все необходимые файловые операции над ним
rename
это на новое место, если местоположение не существует.
Технически, так как rename
перезапишет место назначения, есть вероятность, что параллельные потоки все еще будут конфликтовать. Это очень маловероятно, если у вас есть:
if(!file_exists($lcoation) { rename(...
Вы могли бы использовать md5_file
проверить правильность содержимого файла после этого блока.
Вы можете обеспечить эксклюзивный доступ с помощью семафоров (только для 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);
Используйте режим 'x' вместо 'c' в вашем вызове fopen. И проверьте полученный $fp, если он ложный, файл не был создан текущим потоком, и вы должны перейти к следующему имени файла.
Также, в зависимости от настроек установки вашего PHP, вы можете захотеть поставить @ перед вызовом fopen, чтобы подавить любые предупреждения, если fopen($VMidFile, 'x') не может создать файл, потому что он уже существует.
Это должно работать даже без стада.