2011-08-06 3 views
11

PHP의 설명서 페이지 flock()은 IIS에서 사용하는 것이 안전하지 않음을 나타냅니다. 모든 상황에서 flock에 의지 할 수 없다면 동일한 방법으로 안전하게 달성 할 수있는 다른 방법이 있습니까?PHP flock() 대체

+0

'flock()'도 길이가 0 인 파일을 읽지 않도록하려면 불편합니다. 왜냐하면'flock()'은 파일이 생성 된 후에 만 ​​호출 될 수 있기 때문입니다. 새로운 파일을 생성하여 원자 적으로 쓸 수는 없습니다. – rustyx

+0

또한 필수 파일 잠금은 Linux에서 더 이상 사용되지 않으므로 무리는 실제로 이상적이지 않습니다. (설정하기 위해 약간의 작업이 필요합니다) – Antony

답변

7

가상의 모든 가능한 상황에서 안전하게 동일한 대안을 얻을 수있는 대안이 없습니다. 그것은 컴퓨터 시스템과 the job is not trivial for cross-platform code의 디자인에 의한 것입니다.

flock()을 안전하게 사용해야하는 경우 대신 응용 프로그램 요구 사항을 문서화하십시오.

또는 자신 만의 잠금 메커니즘을 만들 수는 있지만 원자 적이어야합니다. 즉, 잠금을 테스트해야하며 존재하지 않는 경우 잠금을 설정해야합니다. 그 사이에 다른 사람이 잠금을 획득 할 수 없도록해야합니다.

이것은 잠금을 나타내는 잠금 파일을 작성하여 수행 할 수 있지만 존재하지 않는 경우에만 수행 할 수 있습니다. 불행히도 PHP는 그런 방식으로 파일을 생성하는 기능을 제공하지 않습니다.

mkdir()으로 디렉토리를 생성하고 디렉토리가 생성 될 때 true을 반환하고 이미 존재하는 경우 false을 반환 할 수 있으므로 결과로 작업 할 수 있습니다.

+0

완벽합니다. 나는 lockfiles에 대해 생각해 보았지만 PHP에서 구현할 때 문제가있다. 또한 "잠긴"플래그를 설정하고 해제하기 위해 행 잠금을 사용하는 데이터베이스를 사용하려고 생각했지만 느리고 강건하지도 않습니다.그러나, 나는 lockfile 대신에 디렉토리를 사용하는 것을 생각하지 않았습니다! 감사! – Matty

+0

그러나 이것은 *'mkdir()'이 지정된 값을 반환하는 경우에만 작동합니다. 나는 그것이 모든 플랫폼/파일 시스템 조합의 경우인지는 모르겠다. – hakre

+0

'mkdir()'은 적어도 리눅스에서는 제대로 작동합니다. 나는 이것을 Windows 서버에서 테스트 할 것입니다. 예상대로 작동하지 않으면 다시 업데이트하겠습니다. – Matty

1

내 제안은 flock() 대신 mkdir()을 사용하는 것입니다. 이 차이를 보여주는 캐시/읽기, 쓰기에 대한 실제 예입니다 : 이제

$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를 추가 할 수도 있습니다 ile 잠금.

코드가 없으면 코드에 큰 병목 현상이 생깁니다.

중요한 추가 부분은 if (!is_array($data)) {입니다.DB를 쿼리 실패 include

  • 또는 (race condition)
  • 이 경쟁 조건이 발생 빈 문자열의

  • false의 결과로

    1. array() : $ 데이터를 포함 할 수 있기 때문입니다 첫 번째 방문자가이 행을 실행하는 경우 :

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

      다른 방문자가 1 밀리 초 이상이 줄을 실행이 :

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

      이 첫 번째 방문자가 빈 파일을 생성 의미하지만, 두 번째 방문자는 잠금을 만들고 그래서 include는 빈 문자열을 반환합니다.

      $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 
      

      P.S.을

      그래서 당신은 너무 빨리 mkdir()와의 7 배를 사용을 통해 피할 수있는 많은 함정을 보았다 당신이 볼 수 있듯이 mkdir() 앞에 file_exists()을 사용합니다. 이는 my tests (독일어)이 mkdir() 만 사용하여 병목 현상을 유발했기 때문입니다.

  • +1

    "파일을 잠그고 쓰는 동안 스크립트가 멈추는 경우 잠금 파일이 해제됩니다. " - 실행중인 프로세스가 공유 리소스에 대해 갖는 액세스를 제어하기 위해 잠금을 사용하는 경우 이는 매우 바람직한 기능입니다. 프로세스가 종료 된 후에 걸려있는 잠금에 대해 걱정할 필요가 없으므로 처리가 간단 해집니다. 늘 그렇듯이, 궁극적으로 무엇을 성취하려고하는지에 달려 있습니다. – Jason

    +1

    [@ 연산자] (http://php.net/manual/en/language.operators.errorcontrol.php)를 너무 많이 사용하면 여러 가지 방법으로 폭파 될 수 있습니다. 무슨 일이 있었는지 알아. –

    +0

    @JosipRodin 답변을 다시 작성했습니다. 나는 여전히 @ 연산자를 사용하지만 설명을 추가했다. – mgutt

    2

    mkdir을 기반으로 읽기/쓰기 작업을 중심으로 파일 잠금 - 잠금 해제 패턴을 구현할 수 있습니다. 나는 스트레스를 테스트 해봤고 mgutt와는 달리 병목 현상을 발견하지 못했다. 교착 상태 상황을 돌봐야하는데, 그것은 아마도 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: 
        [email protected]($lockname); 
        // attempt to lock, this is the really important atomic action: 
        while ([email protected]($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의 실행 시간 후 오래된 잠금을 제거 스크립트가 잠금 해제되기 전에 종료됩니다. 더 좋은 방법은 스크립트가 실패 할 때 항상 잠금을 제거하는 것입니다. 이것을위한 깔끔한 방법이 있지만 잊어 버렸습니다.

    0

    이 질문에 감사드립니다. 몇 년 전이지만 실용 사례/무리 대치가 건물 가치가 있다고 생각합니다. 나는 이것을 다른 답변에 기반을 두었지만 (비록 PHP 수동 무리 예제를 반영하지만) 파일을 동시에 작성하는 것이 아니라 무리 기능을 대체 할 사람을 찾고있다.) 다음은 충분할 것이라고 생각한다.

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