Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/245.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP信号量替代方案?_Php_Mysql_Concurrency_Semaphore_Flock - Fatal编程技术网

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