How to create a mutex method in PHP per variable value

php lock
predismutex

I need to have a mutex method in PHP so that it keeps exclusivity by a variable value. This is that threads with same value should enter that method one at a time while threads with different values may access that method arbitrarily.

For example, given that method:

/**
 * @param integer $value
 */
function mutexMethod($value)
{
    // Lock for value $value
    echo 'processing';
    sleep(2);
    echo 'this is so heavy';
    // Unlock for value $value
}

For example (I need this to be run through apache):

time |
0    | php > mutexMethod(1); | php > mutexMethod(2); | php > mutexMethod(1);
1    | processing            | processing            |
2    |                       |                       |
3    | this is so heavy      | this is so heavy      | processing
4    |                       |                       |
5    |                       |                       | this is so heavy

As a first solution, I've tried using semaphores but since $value may get any value, I've run out of semaphores space very quickly (I've tried removing the semaphores after using them but this breaks other threads waiting for it and since I cannot know if there are any threads waiting for them, I cannot remove them arbitrarily.

As a second solution, I've tried creating a file with the value $value as name and using flock to lock any other thread. Despite this worked in the CLI, I couldn't manage to make it work through apache. It certainly locked the file but it never released that lock so any other request got stuck until the first one timed out (after 30 seconds).

Finally I though about using MySQL locks but I would like to avoid them as much as we would like to not use the MySQL instance for such things. Ideally we would like a pure PHP solution.

Do you have an idea on how can I solve that problem? I would like to avoid single-semaphore solutions (like having a single semaphore to control access to a file where to keep track of the locks) as this would create a massive bottleneck (specially for those threads with different values).

Thank you very much.

https://github.com/arvenil/ninja-mutex

flock/mysql/redis/memcache adapter

You can try them all and pick the one that works for you

In your case example could look like this

<?php
require 'vendor/autoload.php';

use NinjaMutex\Lock\MemcacheLock;
use NinjaMutex\MutexFabric;

$memcache = new Memcache();
$memcache->connect('127.0.0.1', 11211);
$lock = new MemcacheLock($memcache);
$mutexFabric = new MutexFabric('memcache', $lock);
if ($mutexFabric->get($value)->acquireLock(1000)) {
    // Do some very critical stuff

    // and release lock after you finish
    $mutexFabric->get($value)->releaseLock();
} else {
    throw new Exception('Unable to gain lock for very critical stuff!');
}

Mutex::lock - Manual, I need to have a mutex method in PHP so that it keeps exclusivity by a variable value. This is that threads with same value should enter that method one at a time​  You can create multiple Mutex objects that represent the same named system mutex, and you can use the OpenExisting method to open an existing named system mutex. Note On a server that is running Terminal Services, a named system mutex can have two levels of visibility.

From what I understand you want to make sure only a single process at a time is running a certain piece of code. I myself use lockfiles to have a solution that works on many platforms and doesn't rely on a specific library only available on Linux etc.

For that, I have written a small Lock class. Do note that it uses some non-standard functions from my library, for instance, to get where to store temporary files etc. But you could easily change that.

<?php   
    class Lock
    {
        private $_owned             = false;

        private $_name              = null;
        private $_lockFile          = null;
        private $_lockFilePointer   = null;

        public function __construct($name)
        {
            $this->_name = $name;
            $this->_lockFile = PluginManager::getInstance()->getCorePlugin()->getTempDir('locks') . $name . '-' . sha1($name . PluginManager::getInstance()->getCorePlugin()->getPreference('EncryptionKey')->getValue()).'.lock';
        }

        public function __destruct()
        {
            $this->release();
        }

        /**
         * Acquires a lock
         *
         * Returns true on success and false on failure.
         * Could be told to wait (block) and if so for a max amount of seconds or return false right away.
         *
         * @param bool $wait
         * @param null $maxWaitTime
         * @return bool
         * @throws \Exception
         */
        public function acquire($wait = false, $maxWaitTime = null) {
            $this->_lockFilePointer = fopen($this->_lockFile, 'c');
            if(!$this->_lockFilePointer) {
                throw new \RuntimeException(__('Unable to create lock file', 'dliCore'));
            }

            if($wait && $maxWaitTime === null) {
                $flags = LOCK_EX;
            }
            else {
                $flags = LOCK_EX | LOCK_NB;
            }

            $startTime = time();

            while(1) {
                if (flock($this->_lockFilePointer, $flags)) {
                    $this->_owned = true;
                    return true;
                } else {
                    if($maxWaitTime === null || time() - $startTime > $maxWaitTime) {
                        fclose($this->_lockFilePointer);
                        return false;
                    }
                    sleep(1);
                }
            }
        }

        /**
         * Releases the lock
         */
        public function release()
        {
            if($this->_owned) {
                @flock($this->_lockFilePointer, LOCK_UN);
                @fclose($this->_lockFilePointer);
                @unlink($this->_lockFile);
                $this->_owned = false;
            }
        }
    }
Usage

Now you can have two process that run at the same time and execute the same script

Process 1

$lock = new Lock('runExpensiveFunction');

if($lock->acquire()) {
  // Some expensive function that should only run one at a time
  runExpensiveFunction();
  $lock->release();
}

Process 2

$lock = new Lock('runExpensiveFunction');

// Check will be false since the lock will already be held by someone else so the function is skipped
if($lock->acquire()) {
  // Some expensive function that should only run one at a time
  runExpensiveFunction();
  $lock->release();
}

Another alternative would be to have the second process wait for the first one to finish instead of skipping the code.

$lock = new Lock('runExpensiveFunction');

// Process will now wait for the lock to become available. A max wait time can be set if needed.
if($lock->acquire(true)) {
  // Some expensive function that should only run one at a time
  runExpensiveFunction();
  $lock->release();
}
RAM disk

To limit the number of writes to your HDD/SSD with the lockfiles you could crate a RAM disk to store them in.

On Linux you could add something like the following to /etc/fstab

tmpfs       /mnt/ramdisk tmpfs   nodev,nosuid,noexec,nodiratime,size=1024M   0 0

On Windows you can download something like ImDisk Toolkit and create a ramdisk with that.

sem_get - Manual, A handle returned by a previous call to Mutex::create(). Return Values ¶. A boolean indication of success. Examples ¶. Example #1 Mutex Locking and  The increment_count () function uses the mutex lock simply to ensure an atomic update of the shared variable. The get_count () function uses the mutex lock to guarantee that the 64-bit quantity count is read atomically. On a 32-bit architecture, a long long is really two 32-bit quantities.

If the number of different IDs you need to handle is just a handful you might want to consider using a queue system, eg. an AMQP compatible one like RabbitMQ and have one queue for each ID and then one consumer for each queue.

Mutex lock for Linux Thread Synchronization, If key is 0, a new private semaphore is created for each call to sem_get(). Actually this value is set only if the process finds it is the only process currently the C semget() function), otherwise PHP will be unable to access the semaphore. not on request shutdown but when the variable you store it's resource ID is freed. Due to the initial value 1 for mutex and the placement of mutex.P() and mutex.V() around the critical section, a mutex.P() operation will be completed first, then mutex.V(), and so on. For this pattern, we can let mutex be a counting semaphore, or we can use a more restrictive type of semaphore called a binary semaphore.

std::mutex, HTML · CSS · JavaScript · PHP · JQuery How to Generate a Self-Signed Certificate with OpenSSL in Linux? it may cause a race condition where the values of variables may be unpredictable and vary depending on The mutex can be unlocked and destroyed by calling following two functions :The first function releases  A mutex variable acts like a "lock" protecting access to a shared data resource. The basic concept of a mutex as used in Pthreads is that only one thread can lock (or own) a mutex variable at any given time. Thus, even if several threads try to lock a mutex only one thread will be successful.

Multi-Threaded Programming III - C/C++ Class , When a thread owns a mutex , all other threads will block (for calls to lock ) or receive a false return value (for try_lock ) if they attempt to claim  Note: Unlike other programming languages, PHP has no command for declaring a variable. It is created the moment you first assign a value to it. Think of variables as containers for storing data. A variable can have a short name (like x and y) or a more descriptive name (age, carname, total_volume). Rules for PHP variables:

Thread-local storage, Mutex functions provide for creating, destroying, locking and unlocking mutexes. The call to create the thread has a NULL value for the attributes, which gives the thread Declare a pthread attribute variable of the pthread_attr_t data type. Each thread calls pthread_barrier_wait() when it reaches the barrier, and the call  If you create a function that will only be used from an object context (i.e. you want a dynamic method that can then call methods from the original object, still maintaining access to the object's runtime values) then you can use the following functions I have created (ob_lambda_func and ob_lambda) to enable the dynamic function to easily call

Comments
  • Can you elaborate on the broader problem you're solving? From what I can see, a possible solution might be to store the values in a set, and create accessor functions such that a thread must acquire a mutex to modify set membership. That way, you'll have a synchronized way for threads to "check out" and "check in" $values.
  • I'm trying to solve a race condition on the web so that the users are triggering an algorithm to perform some calculations (relatively expensive and time consuming) for an object with an ID. So the idea is to, based on this ID, "queue" or simply block the requests and process them one by one. If I allow request to be performed at the same time for the same ID, I may loose the previous calculation. The point though, is that requests for different IDs may be performed simultaneously without any drawback.
  • Is the result of the calculation for a particular ID the same each time? Sounds like you should consider memoization
  • Nope, requests are kind of "add X to object ID" and for sake of simplicity, we can consider X different each time. The order of the requests does not matter (i.e.: "add X to object ID and then add Y" is the same as "add Y to object ID and then add X"). We can imagine it as a computationally-expensive addition operation.