PHP flock () альтернатива

Страница документации PHP для flock() указывает, что ее нельзя использовать в IIS. Если я не могу полагаться на flock при любых обстоятельствах, есть ли другой способ, которым я мог бы безопасно достичь того же?

Solutions Collecting From Web of "PHP flock () альтернатива"

Нет альтернативы, позволяющей безопасно достичь этого при всех возможных возможных обстоятельствах. Это по дизайну компьютерных систем, и работа не является тривиальной для кросс-платформенного кода .

Если вам необходимо безопасно использовать flock() , вместо этого документируйте требования для своего приложения.

В качестве альтернативы вы можете создать свой собственный механизм блокировки, однако вы должны убедиться, что он является атомарным. Это означает, что вы должны протестировать блокировку, и если она не существует, установите блокировку, пока вам нужно убедиться, что ничто другое не сможет получить блокировку между ними.

Это можно сделать, создав файл блокировки, представляющий блокировку, но только если он не существует. К сожалению, PHP не предлагает такую ​​функцию для создания файла таким образом.

В качестве альтернативы вы можете создать каталог с mkdir() и работать с результатом, потому что он вернет true когда каталог был создан, и false если он уже существует.

Вы можете реализовать шаблон блокировки блокировки файлов вокруг операций чтения / записи на основе mkdir , так как он является атомарным и довольно быстрым. Я стресс тестировал это, и в отличие от мгутт не нашел узкого места. Вы должны позаботиться о тупиковых ситуациях, хотя это, вероятно, то, что испытал mgutt. Мертвая блокировка – это когда две попытки блокировки продолжают ждать друг друга. Он может быть устранен случайным интервалом при попытках блокировки. Вот так:

 // call this always before reading or writing to your filepath in concurrent situations function lockFile($filepath){ clearstatcache(); $lockname=$filepath.".lock"; // if the lock already exists, get its age: $life=@filectime($lockname); // attempt to lock, this is the really important atomic action: while (!@mkdir($lockname)){ if ($life) if ((time()-$life)>120){ //release old locks rmdir($lockname); $life=false; } usleep(rand(50000,200000));//wait random time before trying again } } 

Затем работайте над своим файлом в пути к файлу, и когда вы закончите, вызовите:

 function unlockFile($filepath){ $unlockname= $filepath.".lock"; return @rmdir($unlockname); } 

Я решил удалить старые блокировки, а также после максимального времени выполнения PHP в случае выхода сценария до его разблокировки. Лучше всего было бы удалять блокировки всегда, когда скрипт терпит неудачу. Для этого есть опрятный способ, но я забыл.

Мое предложение – использовать mkdir() вместо flock() . Это реальный пример для чтения / записи кешей, показывающих различия:

 $data = false; $cache_file = 'cache/first_last123.inc'; $lock_dir = 'cache/first_last123_lock'; // read data from cache if no writing process is running if (!file_exists($lock_dir)) { // we suppress error messages as the cache file exists in 99,999% of all requests $data = @include $cache_file; } // cache file not found if ($data === false) { // get data from database $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); // write data to cache if no writing process is running (race condition safe) // we suppress E_WARNING of mkdir() because it is possible in 0,001% of all requests that the dir already exists after calling file_exists() if (!file_exists($lock_dir) && @mkdir($lock_dir)) { file_put_contents($cache_file, '<?php return ' . var_export($data, true) . '; ?' . '>')) { // remove lock rmdir($lock_dir); } } 

Теперь мы пытаемся добиться того же с flock() :

 $data = false; $cache_file = 'cache/first_last123.inc'; // we suppress error messages as the cache file exists in 99,999% of all requests $fp = @fopen($cache_file, "r"); // read data from cache if no writing process is running if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { // we suppress error messages as the cache file exists in 99,999% of all requests $data = @include $cache_file; flock($fp, LOCK_UN); } // cache file not found if (!is_array($data)) { // get data from database $data = mysqli_fetch_assoc(mysqli_query($link, "SELECT first, last FROM users WHERE id = 123")); // write data to cache if no writing process is running (race condition safe) $fp = fopen($cache_file, "c"); if (flock($fp, LOCK_EX | LOCK_NB)) { ftruncate($fp, 0); fwrite($fp, '<?php return ' . var_export($data, true) . '; ?' . '>'); flock($fp, LOCK_UN); } } 

Важной частью является LOCK_NB чтобы избежать блокировки всех последовательных запросов:

Также можно добавить LOCK_NB в качестве битовой маски к одной из вышеперечисленных операций, если вы не хотите, чтобы flock () блокировался при блокировке.

Без этого код создаст огромное узкое место!

Дополнительная важная часть – if (!is_array($data)) { . Это связано с тем, что $ data может содержать:

  1. array() в результате запроса db
  2. false неудачи include
  3. или пустая строка ( состояние гонки )

Состояние гонки происходит, если первый посетитель выполняет эту строку:

 $fp = fopen($cache_file, "c"); 

и другой посетитель выполняет эту линию через миллисекунду позже:

 if ($fp !== false && flock($fp, LOCK_EX | LOCK_NB)) { 

Это означает, что первый посетитель создает пустой файл, но второй посетитель создает блокировку, и поэтому include возвращает пустую строку.

Таким образом, вы видели много ошибок, которых можно избежать, используя mkdir() и его 7x быстрее:

 $filename = 'index.html'; $loops = 10000; $start = microtime(true); for ($i = 0; $i < $loops; $i++) { file_exists($filename); } echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; $start = microtime(true); for ($i = 0; $i < $loops; $i++) { $fp = @fopen($filename, "r"); flock($fp, LOCK_EX | LOCK_NB); } echo __LINE__ . ': ' . round(microtime(true) - $start, 5) . PHP_EOL; 

результат:

 file_exists: 0.00949 fopen/flock: 0.06401 

PS, как вы можете видеть, я использую file_exists() перед mkdir() . Это связано с тем, что мои тесты (немецкий) вызвали узкие места, используя только mkdir ().

Я понимаю, что этот вопрос несколько лет, но я, похоже, считал, что рабочий пример / замена для стаи может стоить того, чтобы строить. Я основывал это на других ответах, но для тех, кто просто ищет замену функциональности стаи (вместо того, чтобы писать файл одновременно (хотя это и отражает пример ручного рутинного PHP)). Я считаю, что следующего достаточно

 function my_flock ($path,$release = false){ if ($release){ @rmdir($path); } else { return !file_exists($path) && @mkdir($path); } }