gpt4 book ai didi

PHP信号量替代?

转载 作者:行者123 更新时间:2023-12-02 21:22:44 27 4
gpt4 key购买 nike

我正在制作一个小型在线游戏,其中(你知道什么)会有多个用户访问同一个数据库。我的主人没有启用信号量,我真的买不起其他东西(我是一名高中生),所以我正在寻找替代方案。

经过一些研究,我遇到了一些解决方法:

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 相比如何? ?如果一个用户的脚本锁定表而另一个用户请求它,我假设它会阻止? 我能想到的问题是这会锁定整个表,但我真的只需要锁定单个用户(所以不是每个人都在等待)。

似乎人们在说我不需要信号量来更新数据库。我发现您可以使用简单的增量查询,但仍有一些示例需要解决:

如果我需要在提交操作之前检查值怎么办?比如说我需要在允许攻击之前检查防守者是否足够强壮,比如足够的健康。如果是这样,请进行战斗并承受一些伤害,而不要让其他人破坏/弄乱数据。

我的理解是每次都有几个查询,在一定长度的代码上(发送查询,获取数据,增加它,将它存储回),并且数据库不够聪明来处理它?还是我在这里弄错了?对不起

最佳答案

这里真正的问题不是如何实现锁,而是如何伪造可串行化。您被教导在数据库之前或非数据库世界中实现可序列化的方法是通过锁和信号量。基本思想是这样的:

 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
...

另一种看待这个问题的方式是,读/写组合需要被视为一个不能被中断的单一连贯单元。换句话说,它们需要被原子地处理,作为一个原子单元。当人们说数据库“符合 ACID”时,这正是 ACID 中的 A 所代表的含义。在数据库中,我们没有(或者至少应该假装没有)锁,因为我们使用的是事务,它描述了一个原子单元,如下所示:
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:您需要担心一致性,即 ACID 中的 C。

我们希望设计您的模式,以便数据库本身可以识别是否发生了无效的事情,因为如果可以,数据库将阻止它。 备注 :现在我们处于设计领域,必然会有很多不同的方法来解决这个问题。我在这里展示的可能不是最好的,甚至不是好的,但我希望它能够说明解决关系数据库问题所需的思维过程。

所以现在我们关心完整性,也就是说,您的数据库在每次事务之前和之后都处于有效状态。如果数据库像这样处理数据有效性,您可以以幼稚的方式编写事务,如果由于并发而尝试做一些不合理的事情,数据库本身将中止它们。这意味着我们有一个新的责任:我们必须找到一种方法让数据库知道您的语义,以便它可以处理验证。通常,确保有效性的最简单方法是使用主键和外键约束——换句话说,确保行是唯一的或它们肯定引用其他表中的行。我将向您展示游戏中两个场景的思考过程和一些替代方案,希望您能够从那里进行概括。

第一个场景是杀戮。假设您不希望玩家 1 能够在玩家 2 正在杀死玩家 1 的过程中杀死玩家 2。这意味着您希望杀死是原子的。我会这样建模:
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如果发生了意外。您甚至可以将这些东西捆绑到触发检查约束中,并进一步捆绑到存储过程中,以得到真正的细致。

关于第二个例子:能量限制 Action 。最简单的做法是添加一个检查约束:
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

您还可以做其他事情来将其转换为关系完整性问题而不是检查约束,例如在已识别的单位中分配能量并将它们链接到特定的法术调用实例,但这对您来说可能太多了。重新做。这将正常工作。

现在,我不想在把所有内容都放在那里之后再说这个,但是你必须检查你的数据库并确保一切都设置为真正的 ACID 合规性。默认情况下,MySQL 曾经为表提供 MyISAM,这意味着您可以 BEGIN一整天,无论如何,一切都是单独运行的。如果您使用 InnoDB 作为引擎制作表,它或多或少会按预期工作。如果可以,我建议您尝试使用 PostgreSQL,它在开箱即用的情况下更加一致。当然,商业数据库也很强大。另一方面,SQLite 对整个数据库有一个写锁,所以如果那是你的后端,那么整个前面的就没有实际意义了。我不建议将它用于并发写入场景。

总之,问题在于数据库从根本上试图以非常高级的方式为您处理这个问题。 99% 的情况下,您根本不用担心;两个请求恰好在正确的时间发生的几率并没有那么高。您需要采取的所有行动都将发生得如此之快,产生实际竞争条件的可能性几乎为零。但是担心它是好的。毕竟,有一天您可能会编写银行应用程序,知道如何正确地做事很重要。不幸的是,在这种特殊情况下,与关系数据库尝试做的相比,您习惯的锁定原语非常原始。但是数据库正在努力优化速度和完整性,而不是简单的熟悉推理。

如果这对您来说是一个有趣的弯路,我希望您可以查看 Joe Celko 的一本书或一本数据库教科书以获取更多信息。

关于PHP信号量替代?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11045275/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com