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

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

Ответ 1

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

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

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

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

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

Ответ 2

Вы можете реализовать шаблон блокировки блокировки файлов вокруг операций чтения/записи на основе 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:
   [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 в случае выхода script до его разблокировки. Лучше всего было бы удалять блокировки всегда, когда script терпит неудачу. Для этого есть опрятный способ, но я забыл.

Ответ 3

Мое предложение - использовать 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 может содержать:

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

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

$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

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

Ответ 4

Вот моя "альтернатива PHP flock()" - сборка на основе mkdir().

Идея сделать это с помощью mkdir() возникла здесь и здесь.

Моя версия

  • проверяет, есть ли у меня блокировка доступа Это также предотвращает блокировку себя, если я создаю и использую класс несколько раз для одного и того же basedir.name
  • проверяет, был ли создан мой файл блокировки, с помощью которого я запрашиваю блокировку доступа
  • позволяет получить блокировку доступа в том порядке, в котором я пришел, чтобы попросить его
  • останавливает ожидание и зацикливание, если не может получить блокировку доступа в указанное время
  • удаляет мертвые файлы блокировки (= файлы, в которых SID PID больше не существует)

Вы можете использовать PHP-класс следующим образом:

//$dir        (string) = base-directory for the lock-files (with 'files' I mean directories => mode 0644)
// 2       (float/int) = time to wait for lock-access before returning unsuccessful (default is 0 <= try once and return)
//'.my_lock'  (string) = the way you want to name your locking-dirs (default is '.fLock')
$lock = new FileLock($dir, 2, '.my_lock');

//start lock - a locking directory will be created looking like this:
//$dir/.my_lock-1536166146.4997-22796
if ($lock->lock()) {
    //open your file - modify it - write it back
} else { /* write alert-email to admin */ }

//check if I had locked before
if ($lock->is_locked) { /* do something else with your locked file */ }

//unlock - the created dir will be removed (rmdir)
$lock->unlock();

Вот рабочий класс:

//build a file-locking class
define('LOCKFILE_NONE', 0);
define('LOCKFILE_LOCKED', 1);
define('LOCKFILE_ALREADY_LOCKED', 2);
define('LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS', 3);
define('LOCKFILE_FAILED_TO_OBTAIN_LOCK', false);
define('LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT', '');


class FileLock {
    //FileLock assumes that there are no other directories or files in the
    //lock-base-directory named "$name-(float)-(int)"
    //FileLock uses mkdir() to lock. Why?
    //- mkdir() is atomic, so the lock is atomic and faster then saving files.
    //  Apparently it is faster than flock(), that requires several calls to the
    //  file system.
    //- flock() depends on the system, mkdir() works everywhere.

    private static $locked_memory = array();

    public function __construct($lockbasedir, $wait_sec=0, $name='.fLock') {
        $this->lockbasedir = (string)$lockbasedir;
        $this->wait        = (float)$wait_sec;
        $this->name        = (string)$name;

        $this->pid         = (int)getmypid();

        //if this basedir.name was locked before and is still locked don't try to lock again
        $this->is_locked   = empty(self::$locked_memory[$this->lockbasedir . $this->name]) ? LOCKFILE_NONE : LOCKFILE_ALREADY_LOCKED;
    }

    public function lock() {
        if ($this->is_locked) return $this->is_locked;

        $break_time = microtime(true);

        //create the directory as lock-file NOW
        $this->lockdir = "{$this->name}-" . number_format($break_time, 4, '.', '') . "-{$this->pid}";
        @mkdir("{$this->lockbasedir}/{$this->lockdir}", 0644);

        $break_time += $this->wait;

        //try to get locked
        while ($this->wait == 0 || microtime(true) < $break_time) {

            //get all locks with $this->name
            $files = preg_grep("/^{$this->name}-\d+\.\d+-\d+$/", scandir($this->lockbasedir));

            //since scandir() is sorted asc by default
            //$first_file is the next directory to obtain lock
            $first_file = reset($files);

            if (!$first_file) {
                //no lock-files at all
                return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK;
            } elseif ($first_file == $this->lockdir) {
                //Its me!! I'm getting locked :)
                self::$locked_memory[$this->lockbasedir . $this->name] = 1;
                return $this->is_locked = LOCKFILE_LOCKED;
            } elseif (preg_match("/^{$this->name}-\d+\.\d+-{$this->pid}$/", $first_file)) {
                //my process-ID already locked $this->name in another class before
                rmdir("{$this->lockbasedir}/{$this->lockdir}");
                $this->lockdir = $first_file;
                self::$locked_memory[$this->lockbasedir . $this->name] = 1;
                return $this->is_locked = LOCKFILE_ALREADY_LOCKED_IN_OTHER_CLASS;
            }

            //missing lock-file for this job
            if (array_search($this->lockdir, $files) === false) return LOCKFILE_FAILED_TO_OBTAIN_LOCK;

            //run only once
            if ($this->wait == 0) break;

            //check if process at first place has died
            if (!posix_getsid(explode('-', $first_file)[2])) {
                //remove dead lock
                @rmdir("{$this->lockbasedir}/$first_file");
            } else {
                //wait and try again after 0.1 seconds
                usleep(100000);
            }
        }

        return $this->is_locked = LOCKFILE_FAILED_TO_OBTAIN_LOCK_BY_TIMEOUT;
    }

    public function unlock($force=false) {
        if ($force || $this->is_locked == 1) {
            rmdir("{$this->lockbasedir}/{$this->lockdir}");
            self::$locked_memory[$this->lockbasedir . $this->name] = $this->is_locked = LOCKFILE_NONE;
        }
    }
}

Ответ 5

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

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

Ответ 6

На основании mkdir:

// 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);
     }else [email protected]($lockname);
     usleep(rand(50000,200000));//wait random time before trying again
   }
}

Чтобы избежать взаимоблокировки, когда один сценарий в случае, если сценарий завершается до того, как он разблокирован, и один (или несколько сценариев) одновременно не дают результата в $ life = @filectime ($ lockname); потому что все сценарии запускаются одновременно, а затем каталог еще не создан. Чтобы разблокировать, затем позвоните:

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

Ответ 7

Ни один из этих методов не является полностью атомарным.

Я провел несколько тестов, подтверждающих это.

enter image description here

enter image description here

enter image description here

Код для T7, использующий 7 файлов с именами по размеру в кБ:

clearstatcache();
$_DEBUG_ = false;

echo "Lock and flush tester.".time()."<br>";
$time_constant = 1570787996;
die; // Remove this line when you set time_constant 

while ( time()<$time_constant )
 {
 usleep(500);
 }


function test($n, $p, $_DEBUG_){
//  $delay_multiplier = $n*2.5;
  $sname = "$n";    // source
  $tname = "$n.txt";// target
  echo "<h4>$n at ".time()."</h4>";
  for ($i = 0; $i<50; $i++ ){
    $start = microtime(true);
    clearstatcache(); // needed for filesize and touch    
    $st = stat("$sname");
    $original_size = $st['size'];
    if ( $_DEBUG_ )
      echo "; 1) prevAccess by ".$st['mtime']." fsize ".$st['size']."; ";
    $fsize = filesize($sname);
    if ( $original_size <> $fsize )
      die("; fsize total FAILTURE; ");
    if ($fsize === 0)
     echo "! <b>The fsize is 0</b>: stat(): ".$st['size']." ;";    
    else
      {
      // READ OPERATION AND LOCK FOR SHARE
       $locked = false;     
       for ($c = 0; !$locked; $c++):      
         if ( $c > 400)
           break;
         $fp = fopen($sname, "r");
         $locked = flock($fp, LOCK_SH);
         if ($locked)
           break;
         else
           {
           echo "failed to get LOCK_SH;<br>";
           usleep(5000);
           }
       endfor;
       $s = fread($fp, $fsize );
       $success = flock($fp, LOCK_UN);
       if ( $success === false  )
         die("; r flock release failed; ");
       $success = fclose($fp);
       if ( $success === false  )
         die("; fclose failed; ");
       // 10 - loaded data , $p - broser
       if ( $success )
         { 
         $result = touch("$sname",strlen($s),$p);
         if ( $_DEBUG_ )
            echo "; TOUCH: $result;";
         }
       else
         die("fclose FAIL.");
       if ( strlen($s)<60 ) 
          echo "*$s LENGTH:".strlen($s)."<br>";
      }
    clearstatcache();
    $st = stat("$tname");                               
    if ( $_DEBUG_ )
      echo "; 2) prevAccess by ".$st['mtime']." fsize is ".$fsize."; ";

    // WRITE OPERATION WITH LOC_EX
    $fp = fopen($tname, "w");
    $locked = false; 
    /*
    // TOTO NEMÁ VLIV NA ZAMKNUTÍ
    for ($c = 0; !$locked; $c++ ):
      $c++;
      if ( $c > 400)
        break;
      $locked = flock($fp, LOCK_EX);
      if ($locked)
        break;
      else
        {
        echo "failed to get LOCK_EX;<br>";
        usleep(5000);
        }
    endfor;
    */
    $locked = flock($fp, LOCK_EX);
    if ( $locked ) {  // acquire an exclusive lock
        $success = fwrite($fp, $s);
        if ( $success === false)
          echo "; w FAILED;";
        else
          if ( $_DEBUG_ )
                echo " $success B written; ";
        $success = fflush($fp);// flush output before releasing the lock
        if ( $success === false ) 
          echo "; flush FAILED; ";
        $success = flock($fp, LOCK_UN);    // release the lock
        if ( $success === false ) 
          echo "; release FAILED; ";
        $success = fclose($fp);
        if ( $success === false ) 
          echo "; fclose FAILED; ";
        clearstatcache(); // needed for filesize and touch
        $fsize = filesize($tname);
        if ($original_size>$fsize)
            {
            echo "; <b>WRITE FAILED, restoring</b>;";
            $original_fname = "$n";
            $result = copy($original_fname, $tname);
            if ($result == false )
              die(" <b>TOTAL FAILTURE: copy failed.</b>");
            else
              echo " <b>RESTORED</b>;";
            }
        else
        {
          if ($fsize === 0)
           echo "! THE FILE WAS NOT WRITTEN: data length: ".strlen($s)." fsize: $fsize RESOURCE: $fp<br>";    
          if ( $success ) 
              touch("$tname",$fsize,$p);
        }
    } else {
        echo "Couldn't get the lock!";
    }
     $time_elapsed_secs = microtime(true) - $start;
     //usleep( $delay_multiplier + $n*rand(2,6) ); 
     if ( $time_elapsed_secs === 0 )
       echo " FAILED ";
    echo "time: $time_elapsed_secs s<br>"; 
  }
}
// headers to identify originator of the request
switch ( $_SERVER['HTTP_USER_AGENT'] ):
  // FF 1:
  case "Mozilla/5.0 (Windows NT 5.1;) Gecko": 
    $p = 1; break;
  // Chrome:
  case "Mozilla/5.0 (Windows NT 5.1) AppleWebKit Chrome  Safari":
    $p = 2; break;
  // OPERA:
  case "Mozilla/5.0 (Windows NT 5.1) AppleWebKit Chrome Safari":  
    $p = 3; break;
endswitch;

copy("523","523.txt");
copy("948","948.txt");
copy("1371","1371.txt");
copy("1913","1913.txt");
copy("2701","2701.txt");
copy("4495","4495.txt");
copy("6758","6758.txt");

test("523",$p,$_DEBUG_);
test("948",$p,$_DEBUG_);
test("1371",$p,$_DEBUG_);
test("1913",$p,$_DEBUG_);
test("2701",$p,$_DEBUG_);
test("4495",$p,$_DEBUG_);
test("6758",$p,$_DEBUG_);

Код для T8 (тест блокировки mkdir):

clearstatcache();
$_DEBUG_ = false;

echo "Atomicity tester.".time()."<br>";
$time_constant = 1570787996;
die; // Remove this line when you set time_constant 

while ( time()<$time_constant )
 {
 usleep(500);
 }

/*
c is counter for optimalization
first call must have c = 0;
*/
function atomicFuse($n, $c, $disableDelay = false){
  $start = false;
  if ( !file_exists("$n.t") ) 
   $start = mkdir("$n.t");
  if ( !$disableDelay ){
    if ( $start == false )
     {
     $n = $n*30;
     switch($c):      // Delay example increase:
       case 0: break; // 0,01569 total
       case 1: break; // 0,03138 total
       case 2: $n = $n*2; break; // 0,06276 total
       case 3: $n = $n*4; break; // 0,12552 total
       // case 4: You need at least *6 or *8 to get out of problems with extrem times
       case 4: $n = $n*8; break; // 0,25104 t.(upper limit)
       // In case of heavy traffic:
       case 5: $n = $n*8; break; // 0,36087 total extrem
       case 6: $n = $n*10; break; // 0,51777 total extrem
       case 7: $n = $n*20; break; // 1,03554 total extrem
       default: $n = $n*8; break;
     endswitch;
     usleep($n);
     echo ($n)."<br>";
     }
    }
  return $start;
}
function test($n, $p, $_DEBUG_){
  $fp = null;
  $sname = "$n";    // source
  $tname = "$n.txt";// target
  echo "<h4>$n at ".time()."</h4>";
  for ($i = 0; $i<50; $i++ ){
    $start_time = microtime(true);
      {
      $start = atomicFuse($n,0);
      if (!$start) $start = atomicFuse($n,1);
      if (!$start) $start = atomicFuse($n,2);
      if (!$start) $start = atomicFuse($n,3);
      if (!$start) $start = atomicFuse($n,4);
      if (!$start) $start = atomicFuse($n,5);
      if (!$start) $start = atomicFuse($n,6);
      if (!$start) $start = atomicFuse($n,7);
      if (!$start) $start = atomicFuse($n, false);
      if (!$start) echo "<b>Atomicity failed.</b> ";
      if ( $start )
         {
         echo "<b>Atomicity OK.</b> ";
         /////////////////////////////
         // CHECK FILESIZE VALIDITY //
         /////////////////////////////
         clearstatcache(); // needed for filesize and touch    
         $st = stat("$sname");
         $original_size = $st['size'];
         if ( $_DEBUG_ )
           echo "; 1) prevAccess by ".$st['mtime']." fsize ".$st['size']."; ";
         $fsize = filesize($sname);
         if ( $original_size <> $fsize )
           die("; fsize total FAILTURE; ");
         if ($fsize === 0)
          echo "! <b>The fsize is 0</b>: stat(): ".$st['size']." ;";    
         ///////////////////
         // OPEN THE FILE //
         ///////////////////
         $fp = fopen($sname, "r");
         $s = fread($fp, $fsize );
         $success = fclose($fp);
         if ( $success === false  )
           die("; fclose failed; ");
         // 10 - loaded data, $p - browser
         if ( $success )
           { 
           $result = touch("$sname",strlen($s),$p);
           if ( $_DEBUG_ )
              echo "; TOUCH: $result;";
           }
         else
           die("fclose FAIL.");
         if ( strlen($s)<60 ) 
            echo "*$s LENGTH:".strlen($s)."<br>";
         }  
      }
    if ( $start )
      {
      clearstatcache();
      $st = stat("$tname");                               
      if ( $_DEBUG_ )
        echo "; 2) prevAccess by ".$st['mtime']." fsize is ".$fsize."; ";

      // WRITE OPERATION WITH LOC_EX
      $fp = fopen($tname, "w");
      if ( true ) {  // acquire an exclusive lock
          $success = fwrite($fp, $s);
          if ( $success === false)
            echo "; w FAILED;";
          else
            if ( $_DEBUG_ )
                  echo " $success B written; ";
          $success = fflush($fp);// flush output before releasing the lock
          if ( $success === false ) 
            echo "; flush FAILED; ";
          if ( $success === false ) 
            echo "; release FAILED; ";
          $success = fclose($fp);
          if ( $success === false ) 
            echo "; fclose FAILED; ";
          clearstatcache(); // needed for filesize and touch
          $fsize = filesize($tname);
          if ($original_size>$fsize)
              {
              echo "; <b>WRITE FAILED, restoring</b>;";
              $original_fname = "$n";
              $result = copy($original_fname, $tname);
              if ($result == false )
                die(" <b>TOTAL FAILTURE: copy failed.</b>");
              else
                echo " <b>RESTORED</b>;";
              }
          else
            {
              if ($fsize === 0)
               echo "! THE FILE WAS NOT WRITTEN: data length: ".strlen($s)." fsize: $fsize RESOURCE: $fp<br>";    
              if ( $success ) 
                  touch("$tname",$fsize,$p);
            }
          } else {
              echo "Couldn't get the lock!";
             }
      $success = rmdir("$n.t"); // remove atomic fuse
      if ( $success )
        echo "<h4>DIR REMOVED</h4>";
      else
        echo "<h4>DIR NOT REMOVED</h4>";
      } // start
     else 
       echo "skipped"; 
     $time_elapsed_secs = microtime(true) - $start_time;
     if ( $time_elapsed_secs === 0 )
       echo " FAILED ";
     echo "time: $time_elapsed_secs s<br>"; 
  } // for
}

switch ( $_SERVER['HTTP_USER_AGENT'] ):
  case "": 
    $p = 1; break;
  case "":
    $p = 2; break;
  case "":  
    $p = 3; break;
endswitch;

copy("523","523.txt");
copy("948","948.txt");
copy("1371","1371.txt");
copy("1913","1913.txt");
copy("2701","2701.txt");
copy("4495","4495.txt");
copy("6758","6758.txt");

test("523",$p,$_DEBUG_);
test("948",$p,$_DEBUG_);
test("1371",$p,$_DEBUG_);
test("1913",$p,$_DEBUG_);
test("2701",$p,$_DEBUG_);
test("4495",$p,$_DEBUG_);
test("6758",$p,$_DEBUG_);

Примечание: T5-T7 - я не определил, были ли повреждения файла вызваны fflush или fwrite, но именно в этих тестах произошла такая ошибка.

Примечание: T8 - Особая проблема этого теста заключается в том, что он часто слишком долго ждет в начале блока тестирования (в начале функции тестирования). Там даже задержки как 7 секунд ожидания. Но я также попытался удалить эти цифры, и среднее значение не слишком изменилось, поэтому кривая T8 останется неизменной после этого изменения. Проблема здесь заключается в том, что использование задержки в цикле не является идеальным решением проблемы, оно делает вероятность отказа еще выше. Обратите внимание, что под "отказом" я не имею в виду повреждение файла, но пропущение данной атомарной задачи из-за истечения времени ожидания.