TQ
dev.com

Blog about software development

Subscribe

Java's synchronized block in PHP

10 Sep 2018 - by 'Maurits van der Schee'

In Java the "synchronized" block may help to make implementations "thread safe". PHP does not have this language construct, but with the help from "flock" (file lock) and it's "LOCK_EX" (exclusive lock), we can implement this behavior ourselves. This is useful because web servers run multiple threads/processes to execute PHP and these may read/write from the same files. In yesterday's post we presented a lock-less solution, today we further explore the possible uses of "flock".

Implementing a generic "synchronized" block

You may implement a "synchronized" function in PHP (inspired by Java's "synchronized" keyword). This may be implemented using the "flock" function and the "LOCK_EX" flag. The synchronized function creates a lock file with a name based on the topmost entry on the call-stack (which represents the filename and line number of the invocation of the synchronized function). This means you can create multiple independent synchronized blocks as the function automatically names the lock file.

function synchronized($handler)
{
    $name = md5(json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1)[0]));
    $filename = sys_get_temp_dir().'/'.$name.'.lock';
    $file = fopen($filename, 'w');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_EX);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_put_contents_atomic($filename, $string)
{
    return synchronized(function() use ($filename, $string) {
        $tempfile = $filename . '.temp';
        $result = file_put_contents($tempfile, $string);
        $result = $result && rename($tempfile, $filename);
        return $result;
    });
}

My tests (4 readers and 4 writers, 100000 times at >1000 req/sec) have shown that this custom "synchronized" function has no race conditions.

Implementing generic "reader" and "writer" functions

Based on the "synchronized" function you can easily create a generic reader and writer variant in which you pass the "$name" as an argument. The reader can than request a shared lock, while the writer requests an exclusive lock. Below we have implemented the locking solution from previous post using these convenience functions.

function lock_for_reading($name, $handler)
{
    $filename = sys_get_temp_dir().'/'.md5($name).'.lock';
    $file = fopen($filename, 'r');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_SH);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_get_contents_locking($filename)
{
    return lock_for_reading($filename, function() use ($filename) {
        return file_get_contents($filename);
    });
}

function lock_for_writing($name, $handler)
{
    $filename = sys_get_temp_dir().'/'.md5($name).'.lock';
    $file = fopen($filename, 'w');
    if ($file === false) {
        return false;
    }
    $lock = flock($file, LOCK_EX);
    if (!$lock) {
        fclose($file);
        return false;
    }
    $result = $handler();
    flock($file, LOCK_UN);
    fclose($file);
    return $result;
}

function file_put_contents_locking($filename, $string)
{
    return lock_for_writing($filename, function() use ($filename, $string) {
        return file_put_contents($filename, $string);
    });
}

Note that "$name" parameter that you specify as the first argument to the lock functions does not have to be a filename. It should be the same for the corresponding reader and writer and it should be different for unrelated locks.

Did you like this article? Then you also want to read about a locking file cache in PHP.

Enjoy!