【问题标题】:Race Condition in PHP File WritingPHP 文件写入中的竞争条件
【发布时间】:2013-10-21 10:13:40
【问题描述】:

所以我有一个脚本用于接受和处理来自其他脚本和/或应用程序的请求。但是,我的脚本必须完成的任务之一是为每个请求分配一个唯一的、连续的“ID”。

例如,假设应用程序 A 向我的脚本发出 1000 个请求,同时应用程序 B 向我的脚本发出 500 个请求。我必须给他们 1500 个唯一的序列号,比如 2001~3500 给他们每个人。

但是它们之间的顺序无关紧要,所以我可以给它们这样的数字:

#2001 for 1st request from A (henceforth, A1)
#2002 for A2
#2003 for B1
#2004 for A3
#2005 for B2
...and so on...

我尝试创建一个存储该数字的文件和一个具有如下功能的单独锁定文件:

private function get_last_id()
{
    // Check if lock file exists...
    while (file_exists("LAST_ID_LOCKED")) {
        // Wait a little bit before checking again
        usleep(1000);
    }

    // Create the lock file
    touch("LAST_ID_LOCKED");

    // Create the ID file for the first time if required
    if (!file_exists("LAST_ID_INDICATOR")) {
        file_put_contents("LAST_ID_INDICATOR", 0);
    }

    // Get the last ID
    $last_id = file_get_contents("LAST_ID_INDICATOR");
    // Update the last ID
    file_put_contents("LAST_ID_INDICATOR", $last_id + 1);

    // Delete the lock file
    unlink("LAST_ID_LOCKED");

    return $last_id;
}

然而,这段代码会产生一个竞争条件,如果我向他们发送那些 1500 请求,最后一个 ID 将有很多缺失,(例如,只达到 3211 而不是 3500)。

我也尝试过像这样使用flock,但无济于事:

private function get_last_id()
{
    $f = fopen("LAST_ID_INDICATOR", "rw");

    while (true) {
        if (flock($f, LOCK_SH)) {
            $last_id = fread($f, 8192);
            flock($f, LOCK_UN);
            fclose($f);
            break;
        }
        usleep($this->config["waiting_time"]);
    }

    $f = fopen("LAST_ID_INDICATOR", "rw");

    while (true) {
        if (flock($f, LOCK_SH)) {
            $last_id = fread($f, 8192);
            $last_id++;
            ftruncate($f, 0);
            fwrite($f, $last_id);
            flock($f, LOCK_UN);
            fclose($f);
            break;
        }
        usleep($this->config["waiting_time"]);
    }

    return $last_id;
}

那么,我还能做些什么来为这种情况寻找解决方案?

注意事项:由于服务器限制,我仅限于 PHP 5.2,没有信号量之类的东西。

【问题讨论】:

  • 你可以使用uniqid()函数来生成id,它是基于microtime的。该文件的用途是什么?
  • Uniqid 不会这样做,因为它是随机的,我需要数字是连续的。 LAST_ID_INDICATOR 的目的只是存储最后(最大)的 id 号。
  • ,uniqid() 并不是那么随机,虽然不是顺序的,但每次调用函数都会按升序生成 id。使用hexdec() 转换为十进制以了解我的意思。

标签: php concurrency race-condition


【解决方案1】:

由于似乎没有人给出答案,我会给你一个可能的解决方案。

使用Lamport's Bakery Algorithm 作为解决方案的一部分。

编辑:如果您不需要保留订单,过滤器锁定会更好。

显然,这将有其自身的挑战,但值得一试,如果你做对了,它可能会为你想要做的事情做把戏。

既然你提到了信号量,我假设你有足够的知识来理解这个概念。

这可以在“The art of multiprocessor programming”的第 2 章中找到。

【讨论】:

  • 是的,这非常接近我的问题,谢谢。而现在主要的问题是如何在 PHP 中实现它...
  • 那是复杂的部分,您需要阅读更多关于上面 2 个锁的内容,并且创建没有人会在 SO 上发布代码会更加复杂。
【解决方案2】:

如果您可以访问具有锁定功能的数据库,则可以使用它。例如。对于带有框架 PHP 代码的 MySQL:

  1. 创建一个包含一行和一列的表格(如果您不想“双重使用”现有表格):

    $sql = 'CREATE TABLE TABLENAME (COLUMNNAME INTEGER) ENGINE=MyISAM';

    executeSql($sql) ...

  2. 创建一个 PHP 函数来(重新)设置计数器/Id 值:

    $sql = '更新TABLENAME SET COLUMNNAME=0';

    executeSql($sql); ...

  3. 创建一个 PHP 函数来获取一个唯一的、连续的 id:

    $sql = "SELECT GET_LOCK('numberLock',10)";

    executeSql($sql); ...

    $sql = 'SELECT * FROM TABLENAME';

    if ($result = mysqli_query($link, $sql)) {

        $row = mysqli_fetch_row($result);
    
        $wantedId = $row[0];
    
        // do something with the id ...
    
        mysqli_free_result($result);
    

    }

    $sql = 'UPDATE TABLENAME SET COLUMNNAME=COLUMNNAME+1';

    executeSql($sql); ...

    $sql = "SELECT RELEASE_LOCK('numberLock')";

    executeSql($sql); ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 2012-04-17
    • 1970-01-01
    • 2014-10-12
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多