Взаимное исключение PHP (мьютекс) - PullRequest
22 голосов
/ 27 мая 2010

Прочитайте некоторые тексты о блокировке в 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") изменен, и загрузит новый. Правильно?

Ответы [ 5 ]

31 голосов
/ 27 мая 2010

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

Однако, если вы компилируете в поддержке, вы можете использовать несколько других вещей, таких как расширение семафора. (Скомпилируйте 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;
}
1 голос
/ 23 февраля 2011

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

0 голосов
/ 28 марта 2019

Недавно я создал свою собственную простую реализацию мьютексоподобного механизма с использованием функции 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;
}
0 голосов
/ 27 мая 2010

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

0 голосов
/ 27 мая 2010

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

...