Я пытаюсь обновить переменную в APC, и будет много процессов, пытающихся это сделать.
APC не предоставляет функции блокировки, поэтому я рассматриваю возможность использования других механизмов … то, что я нашел до сих пор, это GET_LOCK () и mysql's flock (). Что еще стоит рассмотреть?
Обновление: я нашел sem_acquire, но он блокирует блокировку.
/* 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 } };
Вы можете использовать функцию apc_add для достижения этого, не прибегая к файловым системам или mysql. apc_add
только тогда, когда переменная еще не сохранена; таким образом, обеспечивая механизм блокировки. TTL может использоваться для обеспечения того, чтобы заблокированные держатели логов не удерживали фиксатор навсегда.
Причина, по которой apc_add
является правильным решением, заключается в том, что она избегает условия гонки, которое в противном случае существовало бы между проверкой блокировки и установкой ее на «заблокированную вами». Поскольку apc_add
устанавливает значение только в том случае, если оно еще не установлено («добавляет» его в кеш), он гарантирует, что блокировка не может быть приобретена двумя вызовами одновременно, независимо от их близости во времени. Никакое решение, которое не проверяет и не устанавливает блокировку в одно и то же время, по своей сути будет страдать от этого состояния гонки; требуется одна атомная операция для успешной блокировки без состояния гонки.
Поскольку блокировки APC будут существовать только в контексте этого выполнения php, это, вероятно, не лучшее решение для общей блокировки, поскольку оно не поддерживает блокировки между хостами. Memcache
также предоставляет функцию добавления атомов и, следовательно, может также использоваться с этой методикой – одним из способов блокировки между хостами. Redis
также поддерживает атомные функции «SETNX» и TTL и является очень распространенным методом блокировки и синхронизации между хостами. Howerver, OP запрашивает решение для APC в частности.
Если точкой блокировки является предотвращение попыток нескольких процессов заполнить пустой ключ кеша, почему бы вам не заблокировать блокировку?
$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); }
Если кэш хорош, вы просто рулон с ним. Если в кеше ничего нет, вы получите блокировку. После того, как у вас есть блокировка, вам нужно дважды проверить кеш, чтобы убедиться, что, пока вы ждали, чтобы получить блокировку, кеш не был повторно заселен. Если кеш был заселен, используйте это значение и отпустите блокировку, иначе вы выполните вычисление, заполните кеш и затем отпустите свою блокировку.
Собственно, проверьте, будет ли это работать лучше, чем предложение Питера.
используйте эксклюзивный замок и, если вам удобно, поставьте все остальное, которое попыталось заблокировать файл в 2-3-секундном сне. Если все сделано правильно, ваш сайт будет испытывать зависание в отношении заблокированного ресурса, но не орды скриптов, борющихся за кеширование.
Если вы не возражаете, основывая свою блокировку на файловой системе, вы можете использовать fopen () с режимом «x». Вот пример:
$f = fopen("lockFile.txt", 'x'); if($f) { $me = getmypid(); $now = date('Ymd H:i:s'); fwrite($f, "Locked by $me at $now\n"); fclose($f); doStuffInLock(); unlink("lockFile.txt"); // unlock } else { echo "File is locked: " . get_file_contents("lockFile.txt"); exit; }
Я понимаю, что это год, но я просто наткнулся на этот вопрос, проведя некоторое исследование самостоятельно по блокировке в 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, поскольку он предотвращает ведение блокировки навсегда, если ваш скрипт умирает или что-то в этом роде.
Надеюсь, этот ответ будет полезен для всех, кто спотыкается здесь через год.
Фактически, я обнаружил, что мне вообще не нужна блокировка … учитывая то, что я пытаюсь создать, является карта всех ассоциаций path => path для автозагрузки, неважно, один процесс перезаписывает то, что нашел другой (это очень маловероятно, если оно правильно закодировано), потому что в любом случае данные получат его. Таким образом, решение оказалось «не замками».
У EAccelerator есть методы для этого; eaccelerator_lock
и eaccelerator_unlock
.
Не могу сказать, если это лучший способ справиться с работой, но, по крайней мере, это удобно.
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()); });
Третий необязательный аргумент может пригодиться, если вы используете эту функцию более одного раза для каждого файла.
И последнее, но не менее важное: изменить эту функцию (сохраняя ее подпись) нетрудно, чтобы использовать какой-либо другой механизм блокировки позже, например, если вам удастся найти работу с несколькими серверами.
В настоящее время APC считается недоработанным и мертвым . Это преемник APCu предлагает блокировку через apcu_entry
. Но имейте в виду, что он также запрещает одновременное выполнение любых других функций APCu. В зависимости от вашего варианта использования, это может быть хорошо для вас.
Из руководства:
Примечание. Когда элемент управления входит в
apcu_entry()
блокировка для кэша приобретается исключительно, он отпускается, когда элемент управления покидаетapcu_entry()
: по сути, это превращает телоgenerator
в критический раздел, запрещая два процесса выполнять одни и те же кодовые пути одновременно. Кроме того, он запрещает одновременное выполнение любых других функций APCu, так как они будут получать одну и ту же блокировку.