PHP семафор альтернатива?

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

после некоторых исследований, я наткнулся на пару способы:


A) я сделал пару функций для имитации семафоров, что так же хорошо, как мне нужно, я думаю:

function SemaphoreWait($id) {
    $filename = SEMAPHORE_PATH . $id . '.txt';
    $handle = fopen($filename, 'w') or die("Error opening file.");
    if (flock($handle, LOCK_EX)) {
        //nothing...
    } else {
        die("Could not lock file.");
    }
    return $handle;
}

function SemaphoreSignal($handle) {
    fclose($handle);
}

(я там знаю является ненужным кодом в if-операторе). Что вы думаете? Очевидно, что это не идеально, но общая практика в порядке? Какие-то проблемы? Это первый раз, когда я работаю с параллелизмом, и мне обычно нравится язык низкого уровня, где он имеет немного больше смысла.

В любом случае, я не могу думать о каких-либо "логических недостатках" с этим, и моя единственная забота-скорость; Я читал, что flock довольно медленно.


B) MySQL также предоставляет " блокировку" способности. Как это соотносится с flock? И я предполагаю, что он блокирует, если скрипт одного пользователя блокирует таблицу, а другой запрашивает ее? проблема, о которой я могу думать, заключается в том, что это блокирует всю таблицу, но мне действительно нужны блокировки только для отдельных пользователей (поэтому не все ждут).


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

что делать, если мне нужно проверить значение перед совершением действия? Скажем, мне нужно проверить, достаточно ли силен защитник, прежде чем позволить атаку, например, достаточно здоровья. И если это так, сделайте битву и возьмите некоторый урон, не позволяя никому другому гадить/возиться с данными.

Я понимаю, что есть несколько запросов каждый раз, в течение некоторой длины кода (отправить запрос, получить данные, увеличить его, сохранить его обратно), и база данных не будет достаточно умной, чтобы справиться с этим? Или я ошибаюсь? Жаль

1 ответов


реальная проблема здесь не в том, как реализовать блокировки, а в том, как подделать сериализуемость. Способ, которым вас учат достигать сериализации в мире до или без базы данных, - это блокировки и семафоры. Основная идея такова:--19-->

 lock()
 modify a bunch of shared memory
 unlock()

таким образом, когда у вас есть два одновременных пользователя, вы можете быть уверены, что ни один из них не будет создавать недопустимое состояние. Так что ваш пример сценария, в котором оба игрока атакуют друг друга одновременно и прибыть в противоречивые представления о том, кто победил. Таким образом, вы обеспокоены этим видом чередования:

  User A      User B
    |           |
    V           |
  attack!       |
    |           V
    |         attack!
    V           |
  read "wins"   |
    |           V
    |         read "wins"
    |           |
    V           |
  write "wins"  |
                V
              write "wins"

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

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

 User A       User B
   |            |
   V            |
 attack!        |
   |          attack!
   V            |  
 lock           V
   |          blocking
   V            .
 read "wins"    .
   |            .
   V            .
 write "wins"   .
   |            .
   V            .
 unlock         V
              lock
                |
                V
               ...

другой способ взглянуть на это заключается в том, что комбинацию чтения / записи нужно рассматривать как единую когерентную единицу, которая не может быть прерванный. Другими словами, их нужно рассматривать атомарно, как атомную единицу. Это именно то, что означает A в ACID, когда люди говорят, что база данных " соответствует ACID."В базе данных у нас нет (или, по крайней мере, следует делать вид, что нет) блокировок, потому что мы вместо этого используем транзакции, которые очерчивают атомарную единицу, например:

BEGIN;

SELECT ...
UPDATE ...

COMMIT;

все что между BEGIN и COMMIT ожидается, что будет рассматриваться как атомная единица, поэтому либо все это идет, либо ничего из этого не делает. Получается из того, что полагаться на A недостаточно для вашего конкретного случая использования, потому что нет никакого способа, которым ваши транзакции могут потерпеть неудачу друг друга:

 User A     User B
   |          |
   V          |
 BEGIN        V
   |        BEGIN
   V          |
 SELECT ...   V
   |        SELECT ...
   V          |
 UPDATE       V
   |        UPDATE
   V          |
 COMMIT       V
            COMMIT

особенно, если вы пишете это правильно, где вместо того, чтобы говорить UPDATE players SET wins = 37 скажете вы UPDATE players SET wins = wins + 1, у базы данных нет оснований подозревать, что эти обновления не могут выполняться параллельно, особенно если они работают в разных строках. В результате вам нужно будет использовать больше базы данных foo: вам нужно беспокоиться о согласованности, C в кислоте.

мы хотим создать вашу схему так, чтобы сама база данных могла определить, произошло ли что-то недопустимое, потому что если это возможно, база данных предотвратит это. Примечание: теперь, когда мы находимся на территории дизайна, обязательно будет много разных способов подойти к проблеме. Тот, который я здесь представляю, может быть не лучшим или даже хорошим, но я надеюсь, что он проиллюстрирует мыслительные процессы, которые должны продолжаться для решения проблем с отношениями база данных.

Итак, теперь мы обеспокоены целостность, то есть, что ваша база данных находится в допустимом состоянии до и после каждой транзакции. Если база данных обрабатывает достоверность данных таким образом, вы можете написать свои транзакции наивным способом, и сама база данных прервет их, если они попытаются сделать что-то необоснованное из-за параллелизма. Это означает, что у нас есть новая ответственность: мы должны найти способ сделать базу данных осведомленной о вашей семантике, чтобы она могла займитесь проверкой. В общем, самый простой способ обеспечить валидность-с ограничениями первичного и внешнего ключей,другими словами, гарантировать, что строки уникальны или что они определенно ссылаются на строки в других таблицах. Я собираюсь показать вам мыслительный процесс и некоторые альтернативы для двух сценариев в игре, в надежде, что вы сможете обобщить оттуда.

первый сценарий-убийства. Предположим, вы не хотите, чтобы игрок 1 мог убить игрока 2, если игрок 2 находится в средний убийства игрока 1. Это означает, что вы хотите, чтобы убийства были атомарными. Я бы моделировал это так:

CREATE TABLE players (
  login VARCHAR, 
  -- password hashes, etc.
);

CREATE TABLE lives (
  login VARCHAR REFERENCES players,
  life INTEGER
);

CREATE TABLE alive (
  login VARCHAR,
  life INTEGER,
  PRIMARY KEY (login, life),
  FOREIGN KEY (login, life) REFERENCES lives
);

CREATE TABLE deaths (
  login VARCHAR REFERENCES players,
  life INTEGER,
  killed_by VARCHAR,
  killed_by_life INTEGER,
  PRIMARY KEY (login, life),
  FOREIGN KEY (killed_by, killed_by_life) REFERENCES lives
);

теперь вы можете атомарно создавать новые жизни:

BEGIN;

SELECT login, MAX(life)+1 FROM lives WHERE login = 'login';
INSERT INTO lives (login, life) VALUES ('login', 'new life #');    
INSERT INTO alive (login, life) VALUES ('login', 'new life #');

COMMIT;

и вы можете атомарно убить:

BEGIN;

SELECT name, life FROM alive 
WHERE name = 'killer_name' AND life = 'life #';

SELECT name, life FROM alive
WHERE name = 'victim_name' AND life = 'life #';

-- if either of those SELECTs returned NULL, the victim 
-- or killer died in another transaction

INSERT INTO deaths (name, life, killed_by, killed_by_life)
VALUES ('victim', 'life #', 'killer', 'killer life #');
DELETE FROM alive WHERE name = 'victim' AND life = 'life #';

COMMIT;

теперь вы можете быть совершенно уверены, что эти вещи происходят атомарно, потому что UNIQUE ограничения, подразумеваемые PRIMARY KEY предотвратит создание новых записей о смерти с тем же пользователем и той же жизнью, независимо от того, кто может быть убийцей. Вы также можете вручную проверить, что ваши ограничения выполняются, например, путем выдачи операторов подсчета между шагами и выдачи ROLLBACK если случилось что-то неожиданное. Вы даже можете объединить эти вещи в вызванные ограничения проверки и далее в хранимые процедуры, чтобы стать действительно дотошными.

переход ко второму примеру: маневры ограничения энергии. Самое простое, что нужно сделать, это добавить контрольное ограничение:

CREATE TABLE player (
  login VARCHAR PRIMARY KEY, -- etc.
  energy INTEGER,
  CONSTRAINT ensure_energy_is_positive CHECK(energy >= 0)
);

теперь, если игрок пытается использовать их энергия дважды, вы получите нарушение ограничения в одной из последовательностей:

Player A #1     Player A #2
    |               |
    V               |
  spell             |
    |               V
    V             spell
  BEGIN             |
    |               |
    |               V
    |             BEGIN
    V               |
  UPDATE SET energy = energy - 5;
    |               |
    |               |
    |               V
    |             UPDATE SET energy = energy - 5;
    V               |
  [implied CHECK: pass]
    |               |
    V               |
  COMMIT            V
                  [implied CHECK: fail!]
                    |
                    V
                  ROLLBACK

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

теперь, я ненавижу говорить это после того, как выложил все это там, но ты! .. --23-->будет должны проверить свою базу данных и убедиться, что все настроено на реальное соответствие ACID. По умолчанию MySQL используется для доставки с MyISAM для таблиц, что означает, что вы можете BEGIN весь день, и все было запущено индивидуально в любом случае. Если вы делаете свои таблицы с InnoDB в качестве движка, он работает более или менее так, как ожидалось. Я рекомендую вам попробовать PostgreSQL, если вы можете, это немного более последовательно о таких вещах из коробки. И конечно коммерческие базы данных также являются мощными. SQLite, с другой стороны, имеет блокировку записи для всей базы данных, поэтому, если это ваш бэкэнд, все предыдущее довольно спорно. Я не рекомендую его для параллельных сценариев записи.

в общем, проблема в том, что базы данных принципиально пытаются справиться с этой проблемой для вас на очень высоком уровне. В 99% случаев вы просто не беспокоитесь об этом; шансы двух запросов, происходящих в нужное время, просто не таковы высокий. Все действия, которые вы хотели бы предпринять, будут происходить так быстро, что шансы на создание фактического состояния гонки исчезающе малы. Но об этом стоит беспокоиться. В конце концов, вы можете писать банковские приложения когда-нибудь, и важно знать, как делать все правильно. К сожалению, в этом конкретном случае примитивы блокировки, к которым вы привыкли are очень примитивно по сравнению с тем, что пытаются сделать реляционные базы данных. Но базы данных пытаются оптимизировать для скорости и честность, а не просто знакомые рассуждения.

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