PHP信号量替代方案?
我正在制作一个小的在线游戏,其中(你知道什么)将有多个用户访问同一个数据库。我的主机不支持信号量,我真的负担不起其他东西(我是一名高中生),所以我正在寻找替代方案 经过一些研究,我发现了一些解决方法:PHP信号量替代方案?,php,mysql,concurrency,semaphore,flock,Php,Mysql,Concurrency,Semaphore,Flock,我正在制作一个小的在线游戏,其中(你知道什么)将有多个用户访问同一个数据库。我的主机不支持信号量,我真的负担不起其他东西(我是一名高中生),所以我正在寻找替代方案 经过一些研究,我发现了一些解决方法: A) 我制作了两个函数来模拟信号量,我认为这是我所需要的: function SemaphoreWait($id) { $filename = SEMAPHORE_PATH . $id . '.txt'; $handle = fopen($filename, 'w') or di
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中的A所代表的意思,当人们说数据库是“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
特别是如果你写得很好,而不是说updateplayers SET wins=37
你说updateplayers SET wins=wins+1
,数据库没有理由怀疑这些更新不能并行执行,特别是当它们在不同的行上工作时。因此,您需要使用更多的数据库foo:您需要担心一致性,即ACID中的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;
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