如何使用变量值

时间:2016-04-14 15:24:54

标签: php mutex semaphore flock

我需要在PHP中使用互斥方法,以便通过变量值保持排他性。这是具有相同值的线程应该一次输入一个方法,而具有不同值的线程可以任意访问该方法。

例如,给定该方法:

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

例如(我需要通过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

作为第一个解决方案,我尝试过使用semaphores,但由于$value可能会获得任何价值,因此我很快就会耗尽信号量空间(I' ve尝试在使用它们之后删除信号量,但这会破坏等待它的其他线程,因为我不知道是否有任何线程在等待它们,我无法任意删除它们。

作为第二种解决方案,我尝试创建一个名为$value的文件作为名称,并使用flock锁定任何其他线程。尽管这在CLI中有效,但我无法通过apache使其工作。它当然锁定了文件,但它从未释放该锁定,因此任何其他请求都会被卡住,直到第一个超时(30秒后)。

最后我虽然关于使用MySQL锁,但我想尽可能避免它们,因为我们不希望将MySQL实例用于此类事情。理想情况下,我们想要一个纯PHP解决方案。

您对如何解决该问题有所了解吗?我想避免使用单信号量解决方案(比如只有一个信号量来控制对跟踪锁定的文件的访问),因为这会产生巨大的瓶颈(特别是那些具有不同值的线程)。

非常感谢。

3 个答案:

答案 0 :(得分:0)

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

flock / mysql / redis / memcache adapter

你可以尝试所有这些并选择适合你的那个

在您的情况下,示例可能如下所示

__exit__

答案 1 :(得分:0)

据我了解,您希望确保一次只运行一个特定的代码。我本人使用锁文件来提供一种可在许多平台上使用并且不依赖于仅在Linux等上可用的特定库的解决方案。

为此,我编写了一个小型Lock类。请注意,它使用了我的库中的一些非标准函数,例如,获取临时文件的存储位置等。但是您可以轻松地对其进行更改。

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

用法

现在您可以有两个同时运行并执行相同脚本的进程

过程1

$lock = new Lock('runExpensiveFunction');

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

过程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();
}

另一种选择是让第二个进程等待第一个进程完成,而不是跳过代码。

$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磁盘

要限制使用锁定文件对HDD / SSD的写入次数,您可以创建一个RAM磁盘以将其存储在其中。

在Linux上,您可以将以下内容添加到/etc/fstab

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

在Windows上,您可以下载类似ImDisk Toolkit的文件并使用它创建一个虚拟磁盘。

ImDisk RamDisk Configuration Tool

答案 2 :(得分:0)

如果您需要处理的不同ID数量很少,则可能需要考虑使用队列系统,例如。与AMQP兼容的产品,例如RabbitMQ,每个ID都有一个队列,然后每个队列只有一个使用者。