Как создать метод мьютекса в PHP для значения переменной

Мне нужно иметь метод мьютекса в 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-решение.

У вас есть идея, как я могу решить эту проблему? Я хотел бы избежать решений с одним семафором (например, иметь один семафор для управления доступом к файлу, где нужно отслеживать блокировки), так как это создаст огромное узкое место (особенно для потоков с разными значениями).

Большое спасибо.

3

Решение

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!');
}
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 диск

Чтобы ограничить количество операций записи на жесткий диск / твердотельный накопитель с помощью файлов блокировки, вы можете создать диск RAM для их хранения.

В линуксе Вы можете добавить что-то вроде следующего /etc/fstab

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

На винде Вы можете скачать что-то вроде ImDisk Toolkit и создать виртуальный диск с этим.

ImDisk RamDisk Конфигурационный инструмент

0

Если количество различных идентификаторов, которые вам нужно обработать, является всего лишь небольшим количеством, вы можете рассмотреть возможность использования системы очередей, например. AMQP-совместимый, такой как RabbitMQ, с одной очередью для каждого идентификатора и одним потребителем для каждой очереди.

0
По вопросам рекламы [email protected]