Лучший способ получить блокировку в php
Я пытаюсь обновить переменную в APC, и будет много процессов, пытающихся это сделать.
APC не предоставляет функции блокировки, поэтому я рассматриваю возможность использования других механизмов... то, что я нашел до сих пор, это mysql GET_LOCK() и php flock(). Что еще стоит рассмотреть?
Обновление: я нашел sem_acquire, но он блокирует блокировку.
Ответы
Ответ 1
/*
CLASS ExclusiveLock
Description
==================================================================
This is a pseudo implementation of mutex since php does not have
any thread synchronization objects
This class uses flock() as a base to provide locking functionality.
Lock will be released in following cases
1 - user calls unlock
2 - when this lock object gets deleted
3 - when request or script ends
==================================================================
Usage:
//get the lock
$lock = new ExclusiveLock( "mylock" );
//lock
if( $lock->lock( ) == FALSE )
error("Locking failed");
//--
//Do your work here
//--
//unlock
$lock->unlock();
===================================================================
*/
class ExclusiveLock
{
protected $key = null; //user given value
protected $file = null; //resource to lock
protected $own = FALSE; //have we locked resource
function __construct( $key )
{
$this->key = $key;
//create a new resource or get exisitng with same key
$this->file = fopen("$key.lockfile", 'w+');
}
function __destruct()
{
if( $this->own == TRUE )
$this->unlock( );
}
function lock( )
{
if( !flock($this->file, LOCK_EX | LOCK_NB))
{ //failed
$key = $this->key;
error_log("ExclusiveLock::acquire_lock FAILED to acquire lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Locked\n");
fflush( $this->file );
$this->own = TRUE;
return TRUE; // success
}
function unlock( )
{
$key = $this->key;
if( $this->own == TRUE )
{
if( !flock($this->file, LOCK_UN) )
{ //failed
error_log("ExclusiveLock::lock FAILED to release lock [$key]");
return FALSE;
}
ftruncate($this->file, 0); // truncate file
//write something to just help debugging
fwrite( $this->file, "Unlocked\n");
fflush( $this->file );
$this->own = FALSE;
}
else
{
error_log("ExclusiveLock::unlock called on [$key] but its not acquired by caller");
}
return TRUE; // success
}
};
Ответ 2
Вы можете использовать apc_add для достижения этой цели, не прибегая к файловым системам или mysql. apc_add
выполняется только тогда, когда переменная еще не сохранена; таким образом, обеспечивая механизм блокировки. TTL может использоваться для обеспечения того, чтобы заблокированные держатели логов не удерживали фиксатор навсегда.
Причина apc_add
- правильное решение, потому что оно позволяет избежать условия гонки, которое в противном случае существовало бы между проверкой блокировки и установкой ее на "заблокированную вами". Поскольку apc_add
устанавливает значение, если оно еще не установлено ( "добавляет" его в кеш), он гарантирует, что блокировка не может быть приобретена двумя вызовами одновременно, независимо от их близости во времени. Никакое решение, которое не проверяет и не устанавливает блокировку в одно и то же время, по своей сути будет страдать от этого состояния гонки; требуется одна атомная операция для успешной блокировки без состояния гонки.
Поскольку блокировки APC будут существовать только в контексте этого выполнения php, это, вероятно, не лучшее решение для общей блокировки, поскольку оно не поддерживает блокировки между хостами. Memcache
также предоставляет функцию добавления атомов и, следовательно, может также использоваться с этой методикой - одним из способов блокировки между хостами. Redis
также поддерживает атомарные функции SETNX и TTL и является очень распространенным методом блокировки и синхронизации между хостами. Howerver, OP запрашивает решение для APC в частности.
Ответ 3
Если точка блокировки заключается в предотвращении попыток нескольких процессов заполнить пустой ключ кеша, почему бы вам не заблокировать блокировку?
$value = apc_fetch($KEY);
if ($value === FALSE) {
shm_acquire($SEMAPHORE);
$recheck_value = apc_fetch($KEY);
if ($recheck_value !== FALSE) {
$new_value = expensive_operation();
apc_store($KEY, $new_value);
$value = $new_value;
} else {
$value = $recheck_value;
}
shm_release($SEMAPHORE);
}
Если кеш хорош, вы просто переходите к нему. Если в кеше ничего нет, вы получите блокировку. После того, как у вас есть блокировка, вам нужно дважды проверить кеш, чтобы убедиться, что, пока вы ждали, чтобы получить блокировку, кеш не был повторно заселен. Если кеш был заселен, используйте это значение и освободите блокировку, иначе вы выполните вычисление, заполните кеш и затем отпустите свою блокировку.
Ответ 4
Если вы не возражаете против блокировки вашей файловой системы, вы можете использовать fopen() с режимом "x". Вот пример:
$f = fopen("lockFile.txt", 'x');
if($f) {
$me = getmypid();
$now = date('Y-m-d H:i:s');
fwrite($f, "Locked by $me at $now\n");
fclose($f);
doStuffInLock();
unlink("lockFile.txt"); // unlock
}
else {
echo "File is locked: " . file_get_contents("lockFile.txt");
exit;
}
Смотрите www.php.net/fopen
Ответ 5
Собственно, проверьте, будет ли это работать лучше, чем предложение Питера.
http://us2.php.net/flock
используйте эксклюзивную блокировку и, если вам удобно, поставьте все остальное, которое попыталось заблокировать файл в 2-3-секундном сне. Если все сделано правильно, ваш сайт будет испытывать зависание в отношении заблокированного ресурса, но не орды скриптов, борющихся за кеш-память.
Ответ 6
Я понимаю, что это год, но я просто наткнулся на этот вопрос, сделав некоторое исследование самостоятельно о блокировке в PHP.
Мне кажется, что решение может быть возможно с использованием самого APC. Назовите меня сумасшедшим, но это может быть приемлемый подход:
function acquire_lock($key, $expire=60) {
if (is_locked($key)) {
return null;
}
return apc_store($key, true, $expire);
}
function release_lock($key) {
if (!is_locked($key)) {
return null;
}
return apc_delete($key);
}
function is_locked($key) {
return apc_fetch($key);
}
// example use
if (acquire_lock("foo")) {
do_something_that_requires_a_lock();
release_lock("foo");
}
На практике я мог бы добавить еще одну функцию, чтобы создать ключ для использования здесь, только для предотвращения столкновения с существующим ключом APC, например:
function key_for_lock($str) {
return md5($str."locked");
}
Параметр $expire
является приятной особенностью APC для использования, поскольку он предотвращает ведение вашей блокировки навсегда, если ваш script умирает или что-то в этом роде.
Надеюсь, этот ответ поможет любому, кто споткнется здесь через год.
Ответ 7
На самом деле я обнаружил, что мне вообще не нужна блокировка... учитывая то, что я пытаюсь создать, является карта всех ассоциаций классов = > для автозагрузки, имеет значение, если один процесс перезаписывает то, что нашел другой (это очень маловероятно, если правильно закодировано), потому что данные в конечном итоге получат его. Таким образом, решение оказалось "без блокировок".
Ответ 8
EAccelerator имеет для этого методы; eaccelerator_lock
и eaccelerator_unlock
.
Ответ 9
Нельзя сказать, что это лучший способ справиться с заданием, но, по крайней мере, это удобно.
function WhileLocked($pathname, callable $function, $proj = ' ')
{
// create a semaphore for a given pathname and optional project id
$semaphore = sem_get(ftok($pathname, $proj)); // see ftok for details
sem_acquire($semaphore);
try {
// capture result
$result = call_user_func($function);
} catch (Exception $e) {
// release lock and pass on all errors
sem_release($semaphore);
throw $e;
}
// also release lock if all is good
sem_release($semaphore);
return $result;
}
Использование так же просто, как это.
$result = WhileLocked(__FILE__, function () use ($that) {
$this->doSomethingNonsimultaneously($that->getFoo());
});
Третий необязательный аргумент может пригодиться, если вы используете эту функцию более одного раза для каждого файла.
И последнее, но не менее важное, это не сложно изменить эту функцию (сохраняя ее подпись), чтобы использовать какой-либо другой механизм блокировки на более поздний срок, например. если вам удастся найти работу с несколькими серверами.
Ответ 10
APC теперь считается невоспитанным и мертвым. Это преемник APCu предлагает блокировку через apcu_entry
. Но имейте в виду, что он также запрещает одновременное выполнение любых других функций APCu. В зависимости от вашего варианта использования, это может быть хорошо для вас.
Из руководства:
Примечание: Когда элемент управления входит apcu_entry()
, блокировка для кэша приобретается исключительно, он отпускается, когда элемент управления оставляет apcu_entry()
: по сути, это превращает тело generator
в критический раздел, запрещающий два процесса одновременно выполнять одни и те же кодовые пути. Кроме того, он запрещает одновременное выполнение любых других функций APCu, так как они будут получать один и тот же замок.