PHP文件编写中的竞争条件

So I have a script for accepting and processing request from another scripts and/or applications. However, one of the task has to be done with my script is assigning each request a unique, sequential "ID" to each of them.

For example, let's says that application A is giving a 1000 request to my script, and in the same time, application B is giving 500 request to my script. I have to give them 1500 unique, sequential number, like 2001~3500 to each of them.

The order between them however, does not matter, so I can give them numbers like this :

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

I've tried creating a file that stores that number and a separated lock file with a function like this :

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;
}

This code, however, would create a race condition, where if I send them those 1500 request, the last ID will have quite a number missings, (e.g. only reach 3211 instead of 3500).

I've also tried using flock like this, but to no avail :

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;
}

So, what else can I do to look for a solution for this situation?

Notes: Due to server limitation, I'm limited to PHP 5.2 without something like semaphores and such.

Since no-one seems to be giving an answer, I'll give you a possible solution.

Use the Lamport's Bakery Algorithm as part of your solution.

Edit: The filter lock would work even better if you don't need the order preserved.

Obviously this will have its own challenges implementing but it's worth a try and if you get it right, it might just do the trick for what you want to do.

Since you mentioned semaphores, I assume you know enough knowledge to understand the concept.

This can be found in chapter 2 of "The art of multiprocessor programming".

If you have access to a database whith locking capabilities you can use that. E.g. for MySQL with skeleton PHP code:

  1. Create a table with one row and one column (if you do not want "dual-use" of an already existing table):

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

    excecuteSql($sql) ...

  2. Create a PHP function to (re)set the counter/Id value:

    $sql = 'UPDATE TABLENAME SET COLUMNNAME=0';

    executeSql($sql); ...

  3. Create a PHP function to get a unique, successive 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); ...