Подпрограмма блокировки Perl

Я написал CGI-скрипт. Одна из его подпрограмм не может выполняться одновременно. то есть один и тот же пользователь (или хакер) запускает два экземпляра в одно и то же время). Как я могу этого избежать?

Я сделал следующий обходной путь, используя файлы блокировки, но я не уверен, что это безопасно:

unless (-e $filelock) {
    sub_that_should_be_locked();
}


sub sub_that_should_be_locked {
    open FILE, ">", $filelock;
    flock DATAFILE, LOCK_EX;
    close FILE;

    ...Code that cannot be executed at the same time...   
    ...Code that cannot be executed at the same time...

    unlink $filelock;
}

Не должно быть ожидания/очереди, параллельные процессы никогда не должны вызывать sub_that_should_be_locked


person user3518089    schedule 22.04.2015    source источник
comment
Это по-прежнему восприимчиво к условиям гонки. Если два потока достигают вашей строки unless одновременно, то оба будут успешно продолжать работу, исходя из неверного предположения, что они являются единственной запущенной копией.   -  person Phylogenesis    schedule 22.04.2015
comment
@ikegami сказал, что вы хотите полностью избегать вызова sub_that_should_be_locked. Это правильно?   -  person Borodin    schedule 22.04.2015
comment
Да, sub_that_should_be_locked никогда не следует запускать/вызывать одновременно, потому что это подпрограмма, которая получает денежный баланс и вычитает все средства, если sub_that_should_be_locked запускается одновременно, денежный баланс станет отрицательным, а не 0, как я хочу.   -  person user3518089    schedule 22.04.2015
comment
Мы все знаем, что он не должен работать одновременно в нескольких процессах. Возникает вопрос: что должно произойти, если два процесса попытаются запустить его одновременно? Должен ли второй процесс просто пропустить вызов sub_that_should_be_locked или второй процесс должен дождаться выхода первого процесса из sub_that_should_be_locked?   -  person ikegami    schedule 22.04.2015
comment
нет ожидания, второй процесс должен пропустить вызов sub_that_should_be_locked, потому что доля секунды тоже может вызвать проблемы, так как баланс может обновляться не так быстро   -  person user3518089    schedule 22.04.2015
comment
Ваше указанное требование можно выполнить, просто удалив все вызовы sub_that_should_be_locked, но я сомневаюсь, что вы этого хотите. При каких обстоятельствах следует вызывать подпрограмму?   -  person Borodin    schedule 22.04.2015


Ответы (2)


Это небезопасно. В вашем коде есть состояние гонки, так как между -e и open есть время. Никогда не используйте -e с файлами блокировки.

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

Обратите внимание, что для того, чтобы это работало, вы должны удерживать блокировку в течение всего времени выполнения sub_that_should_be_locked. (Ваш код выпускает его, как только получает.)

use Fcntl qw( LOCK_EX LOCK_NB );

sub get_lock_nb {
   my ($qfn) = @_;

   open(my $fh, '+>:raw', $qfn)
      or die("Unable to open file \"$qfn\": $!\n");

   if (!flock($fh, LOCK_EX | LOCK_NB)) {
      return undef if $!{EWOULDBLOCK};
      die("Unable to lock file \"$qfn\": $!\n");
   }

   return $fh;
}

sub sub_that_should_be_locked {
   ... Mutually exclusive code ...
}

{
   my $lock = get_lock_nb("file.lock");
   sub_that_should_be_locked() if $lock;
}
person ikegami    schedule 22.04.2015
comment
PS. Обратите внимание, что я избегал использования глобальной переменной для дескриптора файла (open my $fh вместо open FILE). По возможности избегайте использования глобальных переменных. - person ikegami; 22.04.2015
comment
Каков конечный результат? Все одновременные вызовы sub_that_should_be_locked будут отклонены? Все будут отвергнуты, кроме первого? Или что-то другое? я немного запутался, я знаком с perl, но не знаком с внутренностями flock/linux - person user3518089; 22.04.2015
comment
@ user3518089, как и ваш код, второй процесс просто избегает вызова sub_that_should_be_locked, если процесс уже выполняет его. Как вы и просили, это не ждет. Если у вас другой результат, дайте мне знать (отредактировав свои вопросы и оставив здесь комментарий о том, что вы отредактировали свой вопрос). - person ikegami; 22.04.2015
comment
это именно так, я просто надеюсь, что это работает, потому что у меня нет надежного способа проверить это: D - person user3518089; 22.04.2015
comment
@user3518089 user3518089, поместите sleep 15; в sub_that_should_be_locked, и должно быть легко увидеть, что происходит. - person ikegami; 22.04.2015
comment
я тестирую код без параллельных процессов, и он умирает при невозможности заблокировать файл /tmp/file.lock: неверный аргумент - person user3518089; 22.04.2015
comment
Я предполагаю, что вы забыли сделать use Fcntl qw( LOCK_EX LOCK_NB );. Если это так, это также означает, что вы забыли сделать use strict; use warnings; (как и всегда), так как они поймали бы эту ошибку раньше. - person ikegami; 22.04.2015
comment
решено с помощью use Fcntl qw( LOCK_EX LOCK_NB ), большое спасибо - person user3518089; 22.04.2015
comment
Добавил use Fcntl ... к моему ответу. Я полагал, что вы знали, что вам это нужно, поскольку ваш существующий код уже нуждался в этом. - person ikegami; 22.04.2015
comment
А как насчет use strict; use warnings;? Добавьте тех. Если вы этого не сделаете, многие ошибки будут скрыты от вас. - person ikegami; 22.04.2015
comment
Это может быть странно, но я добавляю их только после того, как проект закончен, поэтому я спокойно все полирую. Кстати, я проверил код с вашим предложением sleep 15;, которое доказало, что код отлично работает для того, что мне нужно. - person user3518089; 22.04.2015
comment
@ user3518089: Да, это очень странно. Значит, вы думаете об исправлении ошибок как о шлифовке? И ваш проект закончен до того, как вы нашли все ошибки? - person Borodin; 22.04.2015
comment
@user3518089 user3518089, если у вас нет ошибок, добавление use strict; use warnings; ничего не даст. Я не понимаю, почему вы хотите найти ошибки только после того, как закончите (чего вы явно не хотите, если ошибки все еще есть). - person ikegami; 22.04.2015

Потоки, которые вы хотите синхронизировать, не должны создавать и удалять файлы блокировки.

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

Нравится

open my $lock_fh, '<', $lockfile or die $!;
flock $lock_fh, LOCK_EX;

как его первое действие. Это приостановит поток до тех пор, пока он не окажется в очереди, после чего файл будет неявно закрыт в конце подпрограммы (и, таким образом, освободит его блокировку), потому что $lock_fh выходит за пределы области видимости.

person Borodin    schedule 22.04.2015
comment
Это не соответствует требованиям OP, поскольку блокируется, если кто-то другой держит блокировку. // Кроме того, ожидание того, что файлы блокировки уже существуют, является ненужным ограничением, которое не позволяет вам размещать файлы блокировки в /tmp - person ikegami; 22.04.2015
comment
@ikegami: я не решаюсь ответить, поскольку вы склонны вести веселый танец в своих диалогах комментариев, но как это противоречит заявлению ОП? - person Borodin; 22.04.2015
comment
Он не хочет блокировать, если кто-то держит замок; Вместо этого Он хочет полностью избежать вызова sub_that_should_be_locked. Это было целью -e проверки, и он также прямо заявил об этом. - person ikegami; 22.04.2015
comment
@ikegami: я считаю, что ваше решение должно начинаться с описания проблемы, на которую оно отвечает. - person Borodin; 22.04.2015