Как мы побороли flock
Дано: файл, в который часто пишется информация и часто из него считывается. Например, простой текстовый счетчик на страницу.Проблема: рано или поздно, но обязательно случится ситуация, когда одновременно к файлу будет несколько обращений — и на запись и на чтение. И вся информация, натурально, потеряется.
Чтобы этого не случилось, существует специальный оператор в PHP — flock. Который, по идее, должен с этим бороться — сначала мы получаем эксклюзивные права доступа к файлу, пишем туда, что хотим, а потом эти права теряем.
В идеале это работает. Но — цитирую документацию по PHP — «flock() will not work on NFS and many other networked file systems. Check your operating system documentation for more details». Если почитать все ту же документацию, то в комментариях пользователей приводится множество способов побороть flock, вплоть до самых экзотичных.
Стоит ли говорить, что у меня ни сам flock, ни другие способы просто не работали?
В конце концов, я набрел в одном англоязычном блоге на интересную фразу как раз по этому поводу: «A file rename operation is guaranteed to be atomic by the operating system, and is very efficient as file functions are always highly optimized».
То есть, rename — это атомарная, сиречь неделимая функция операционной системы. К тому же она очень эффективная, потому что файловые функции всегда хорошо оптимизированы.
Так что я попробовал побороть flock следующим образом: сначала пишем все, что нам нужно, в временный файл (не забудьте выставить правильный chmod на директорию, в которую он будет писаться), а потом просто переименовываем этот файл в тот файл, который нужно. Причем в unix-овых системах старый файл удалять нет необходимости — все будет перезаписано сверху.
Я написал две функции. Первая функция — это функция записи в файл, вторая — чтения из файла. Эти две функции интенсивно работают у меня вот уже больше месяца, и пока не было ни одного сбоя.
Запись в файл:
function S_fw ($COUNT_FILE, $text)
{
$z=rand (0,100000);
$fp = @fopen($COUNT_FILE.".".$z, "wb+");
@fwrite($fp,$text);
@fclose($fp);
if (@rename ($COUNT_FILE.".".$z, $COUNT_FILE)==false)
{
@unlink ($COUNT_FILE);
@rename ($COUNT_FILE.".".$z, $COUNT_FILE);
}
}
Чтение из файла (flock здесь стоит «на всякий случай»):
function S_fr ($COUNT_FILE)
{
clearstatcache();
$fp = @fopen($COUNT_FILE, "rb");
@flock ($fp,LOCK_SH);
$conts=@fread ($fp, filesize ($COUNT_FILE));
@fclose ($fp);
@flock($fp, LOCK_UN);
return $conts;
}
Пользуйтесь на здоровье.
PS. Мне тут подсказывают про функцию tempnam. Использовать ее будет более правильно, чем rand.
PSS. Прислали ссылку на еще один способ, как это можно сделать. Я не проверял.