Взаимное исключение PHP (мьютекс)

Прочтите несколько текстов о блокировках в PHP.
Все они, в основном, ведут на http://php.net/manual/en/function.flock.php .

На этой странице рассказывается об открытии файла на жестком диске!!

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

Можете ли вы еще утешить меня восхитительной новостью?

Изменить:

В связи с некоторыми ответами, которые я получил здесь, я хочу задать следующий вопрос:
Мой скрипт будет выполняться только одним потоком или несколькими? Потому что если это один, то мне явно не нужен мьютекс. Есть краткий ответ?

Что именно я пытаюсь сделать

Вопрос от ircmaxell.
Вот история:

У меня есть два ftp-сервера. Я хочу иметь возможность показывать на своем веб-сайте, сколько онлайн-пользователей находятся в сети.
Итак, я подумал, что эти ftp-серверы будут "ОТПРАВЛЯТЬ" свою статистику на определенную страницу PHP-скрипта. Предположим, что URL-адрес этой страницы — «http://mydomain.com/update.php».

На главной странице сайта ("http://mydomain.com/index.php") я буду отображать совокупная статистика (онлайн-пользователи).

Вот и все.

Моя проблема в том, что я не уверен, что когда один ftp-сервер обновляет свою статистику, а другой делает то же самое, информация будет смешанной.
Например, при многопоточности; Два потока одновременно увеличивают некоторую переменную "int". Этого не произойдет, если вы не выполните синхронизацию между ними.
Итак, у меня возникнут проблемы? Да нет Может быть?

Возможное решение

Усердно размышляя над этим весь день, у меня есть идея, и я хочу, чтобы вы высказали свое мнение.
Как я уже сказал, эти ftp-серверы будут публиковать свою статистику каждые 60 секунд.
Я думаю о том, чтобы иметь этот файл. «stats.php».
Он будет включен в сценарий обновления, на который переходят ftp-серверы («update.php»), и на страницу «index.php», где посетители видят, сколько пользователей находится в сети.
Теперь при обновлении ftp-сервера скрипт в "update.php" изменит "stats.php" с новой кумулятивной статистикой.
Сначала он будет считывать статистику, включенную в "stats.php", затем накапливать, а затем переписать этот файл.

Если я не ошибаюсь, PHP обнаружит, что файл ("stats.php") изменен, и загрузит новый. Правильный?


person Poni    schedule 27.05.2010    source источник
comment
PHP вообще поддерживает многопоточность? Просматривая PHP API, я не вижу ничего, что могло бы быть связано с потоком...   -  person tloach    schedule 27.05.2010
comment
По какой-то причине я думал, что PHP поддерживает многопоточность таким образом, что он будет выполнять один и тот же скрипт для нескольких клиентов одновременно.   -  person Poni    schedule 27.05.2010
comment
Итак, у меня есть статическая переменная, которая будет содержать некоторые данные, которые обновляются несколькими источниками и передаются нескольким клиентам. Это может произойти одновременно, поэтому мне нужен какой-то способ синхронизации.   -  person Poni    schedule 27.05.2010
comment
Все, пожалуйста, проверьте stackoverflow.com/questions/2922959/ .   -  person Poni    schedule 27.05.2010
comment
@tloach, PHP — это язык. Он может поддерживать все со своими библиотеками расширений.   -  person Pacerier    schedule 19.01.2015


Ответы (5)


Ну, большая часть PHP работает в другом пространстве процесса (существует несколько реализаций потоков). Легкий - стадо. Гарантированно работает на всех платформах.

Однако, если вы компилируете в поддержку, вы можете использовать несколько других вещей, таких как расширение Semaphore. (Скомпилируйте PHP с --enable-sysvsem). Затем вы можете сделать что-то вроде (обратите внимание, sem_acquire() должен блокироваться. Но если по какой-то причине это невозможно, он вернет false):

$sem = sem_get(1234, 1);
if (sem_acquire($sem)) {
    //successful lock, go ahead
    sem_release($sem);
} else {
    //Something went wrong...
}

Другие варианты, которые у вас есть, это уровень пользователя MySQL блокировки GET_LOCK('name', 'timeout') или создать свою собственную, используя что-то вроде APC или XCache (обратите внимание, это не будет настоящей блокировкой, поскольку могут быть созданы условия гонки, когда кто-то другой получает блокировку между вашей проверкой и принятием блокировки) .

Изменить: Чтобы ответить на ваш отредактированный вопрос:

Все зависит от конфигурации вашего сервера. PHP может работать в многопоточном режиме (каждый запрос обслуживается отдельным потоком) или в многопроцессорном режиме (когда каждый запрос обслуживается отдельным процессом). Все зависит от конфигурации вашего сервера...

ОЧЕНЬ редко PHP будет обслуживать все запросы последовательно, только один процесс (и один поток) обслуживает все запросы. Если вы используете CGI, то по умолчанию используется несколько процессов. Если вы используете FastCGI, скорее всего, это многопроцессорный и многопоточный. Если вы используете mod_php с Apache, это зависит от типа работника:

  1. mpm_worker будет одновременно многопроцессорным и многопоточным, а количество процессов определяется переменной ServerLimit.
  2. prefork будет мультипроцессным
  3. Perchild также будет многопроцессорным

Изменить: Чтобы ответить на ваш второй отредактированный вопрос:

Это довольно легко. Сохраните его в файле:

function readStatus() {
    $f = fopen('/path/to/myfile', 'r');
    if (!$f) return false;
    if (flock($f, LOCK_SH)) {
        $ret = fread($f, 8192);
        flock($f, LOCK_UN);
        fclose($f);
        return $ret;
    }
    fclose($f);
    return false;
}

function updateStatus($new) {
    $f = fopen('/path/to/myfile', 'w');
    if (!$f) return false;
    if (flock($f, LOCK_EX)) {
        ftruncate($f, 0);
        fwrite($f, $new);
        flock($f, LOCK_UN);
        fclose($f);
        return true;
    }
    fclose($f);
    return false;
}

function incrementStatus() {
    $f = fopen('/path/to/myfile', 'rw');
    if (!$f) return false;
    if (flock($f, LOCK_EX)) {
        $current = fread($f, 8192);
        $current++;
        ftruncate($f, 0);
        fwrite($f, $current);
        flock($f, LOCK_UN);
        fclose($f);
        return true;
    }
    fclose($f);
    return false;
}
person ircmaxell    schedule 27.05.2010
comment
Другое пространство процесса, но я могу иметь одну статическую переменную (также известную как singleton), которая используется всеми клиентами - как это получается? - person Poni; 27.05.2010
comment
Эта блокировка на уровне пользователя MySQL хороша, но требует TCP-соединения и машины/экземпляра MySQL. Лучше использовать файловую блокировку.. Спасибо! - person Poni; 27.05.2010
comment
Вы не можете использовать переменную в нескольких экземплярах php. Что ж, вы могли бы, если бы вы сериализовали его и сохранили в глобальном хранилище (например, APC, memcached, MySQL и т. д.). Но каждый клиент будет создавать свой собственный экземпляр. Что именно ты пытаешься сделать? - person ircmaxell; 27.05.2010
comment
Что касается вашего последнего редактирования (Изменить: чтобы ответить на ваш второй отредактированный вопрос), вы говорите о моем возможном редактировании решения? - person Poni; 27.05.2010
comment
!!!!! Предупреждение: Блокировки на основе файлов НЕНАДЕЖНЫ, если у вас есть веб-сайт с высокой нагрузкой. Вы не можете использовать мьютекс для файлов. - person Slawek; 24.01.2011
comment
@Славек, как так? Впервые слышу об этом и не могу найти ничего, что могло бы подтвердить ваше утверждение. (Они ненадежны при монтировании NFS, но это нечто другое. Они являются добровольными, поэтому нет гарантии, что никто не использует файл, но здесь это не имеет значения.) - person ikegami; 04.04.2014
comment
@ikegami Единственная проблема, о которой я знаю, это сценарий, в котором файл еще не существует и перезаписывается. Тем не менее, лучше использовать семафор/мьютекс функции System V IPC. - person ficuscr; 23.05.2014
comment
@ficuscr, вы не можете заблокировать несуществующий файл. - person ikegami; 23.05.2014

Вопрос в том, где вы будете хранить статистику, которую FTP-серверы отправляют с помощью POST в ваш файл update.php? Если это локальный файл, то вам ответил ircmaxell во втором посте. Вы также можете сделать это с помощью мьютекса - функции семафора. Другим решением является использование таблицы MySQL MyISAM для хранения статистики и использование чего-то вроде update info_table set value = value + 1. Он должен заблокировать таблицу и сериализовать ваши запросы, и у вас не будет проблем.

person Milen Boev    schedule 23.02.2011

Да, это правда, поскольку PHP запускается Apache, а Apache может организовывать потоки выполнения по своему усмотрению (см. различные рабочие модели). Поэтому, если вы хотите получить доступ к ресурсу по одному, вы либо блокируете файл (что хорошо, например, если вы имеете дело с заданиями cron), либо полагаетесь на механизм транзакций базы данных, функции ACID и блокировку ресурсов базы данных. если вы имеете дело с данными.

person Palantir    schedule 27.05.2010

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

function mutex_lock($id, $wait=10)
{
  $resource = fopen(storage_path("app/".$id.".lck"),"w");

  $lock = false;
  for($i = 0; $i < $wait && !($lock = flock($resource,LOCK_EX|LOCK_NB)); $i++)
  {
    sleep(1);
  }

  if(!$lock)
  {
    trigger_error("Not able to create a lock in $wait seconds");
  }

  return $resource;
}

function mutex_unlock($id, $resource)
{
  $result = flock($resource,LOCK_UN);
  fclose($resource);

  @unlink(storage_path("app/".$id.".lck"));

  return $result;
}
person Mark Baaijens    schedule 28.03.2019

PHP не поддерживает многопоточность, каждый запрос (и, следовательно, каждый PHP-скрипт) будет выполняться только в одном потоке (или даже в процессе, в зависимости от того, как вы запускаете PHP).

person Daff    schedule 27.05.2010
comment
PHP поддерживает многопоточность, вы можете легко сделать это с помощью асинхронного HTTP. - person Slawek; 24.01.2011
comment
Подождите, асинхронный HTTP — это клиентская сторона, это не означает, что серверная часть работает в многопоточном режиме (каждый асинхронный запрос выполняется как однопоточный PHP-скрипт). - person Daff; 24.01.2011
comment
pcntl_fork позволит вам сделать настоящую многопоточность, интересно, почему никто никогда не упоминает об этом. - person CodeReaper; 16.08.2012
comment
Никакой pcntl_fork не позволяет выполнять многопроцессорную обработку, а не многопоточность. У меня есть некоторый опыт в этом :) github.com/shaneharter/PHP-Daemon - person Shane H; 26.10.2012
comment
Хотя PHP не поддерживает запуск нескольких потоков, он работает в многопоточной или многопроцессорной среде. - person PaulH; 04.07.2018