Мне нужно иметь метод мьютекса в 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
В качестве первого решения я попытался использовать семафоры но с тех пор $value
может получить любое значение, я исчерпал пространство семафоров очень быстро (я попытался удалить семафоры после их использования, но это прерывает другие потоки, ожидающие его, и так как я не могу знать, есть ли какие-либо потоки, ожидающие их, я не могу удалить их произвольно.
В качестве второго решения я попытался создать файл со значением $value
как имя и использование flock
заблокировать любой другой поток. Несмотря на то, что это работало в CLI, мне не удалось заставить его работать через apache. Он, конечно, заблокировал файл, но никогда не снимал эту блокировку, поэтому любой другой запрос зависал до истечения первого тайм-аута (через 30 секунд).
Наконец, я подумал об использовании блокировок MySQL, но я бы хотел избежать их столько, сколько мы хотели бы не использовать экземпляр MySQL для таких вещей. В идеале мы хотели бы получить чистое PHP-решение.
У вас есть идея, как я могу решить эту проблему? Я хотел бы избежать решений с одним семафором (например, иметь один семафор для управления доступом к файлу, где нужно отслеживать блокировки), так как это создаст огромное узкое место (особенно для потоков с разными значениями).
Большое спасибо.
https://github.com/arvenil/ninja-mutex
адаптер flock / mysql / redis / memcache
Вы можете попробовать их все и выбрать тот, который работает для вас
В вашем случае пример может выглядеть так
<?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!');
}
Из того, что я понимаю, вы хотите убедиться, что только один процесс одновременно выполняет определенный фрагмент кода. Я сам использую файлы блокировки, чтобы иметь решение, которое работает на многих платформах и не полагается на конкретную библиотеку, доступную только в 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 для их хранения.
В линуксе Вы можете добавить что-то вроде следующего /etc/fstab
tmpfs /mnt/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=1024M 0 0
На винде Вы можете скачать что-то вроде ImDisk Toolkit и создать виртуальный диск с этим.
Если количество различных идентификаторов, которые вам нужно обработать, является всего лишь небольшим количеством, вы можете рассмотреть возможность использования системы очередей, например. AMQP-совместимый, такой как RabbitMQ, с одной очередью для каждого идентификатора и одним потребителем для каждой очереди.