лучший способ получить блокировку в php - PullRequest
17 голосов
/ 28 ноября 2008

Я пытаюсь обновить переменную в APC, и многие процессы попытаются это сделать.

APC не обеспечивает функциональность блокировки, поэтому я рассматриваю возможность использования других механизмов ... что я нашел до сих пор, это mysql GET_LOCK () и php's flock () Что-нибудь еще стоит рассмотреть?

Обновление: я нашел sem_acquire, но, похоже, это блокирующая блокировка.

Ответы [ 10 ]

15 голосов
/ 13 октября 2010
/*
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
    }
};
8 голосов
/ 07 октября 2012

Вы можете использовать функцию apc_add , чтобы достичь этого, не прибегая к файловым системам или mysql. apc_add успешно выполняется только тогда, когда переменная еще не сохранена; Таким образом, обеспечивается механизм блокировки. TTL может использоваться, чтобы гарантировать, что поддельные блокировщики не будут удерживать блокировку вечно.

Причина, по которой apc_add является правильным решением, заключается в том, что он избегает состояния гонки, которое могло бы существовать между проверкой блокировки и установкой ее на «заблокированную вами». Поскольку apc_add устанавливает значение только в том случае, если оно не установлено уже («добавляет» его в кэш), это гарантирует, что блокировка не может быть получена двумя вызовами одновременно, независимо от их близости в время. Ни одно решение, которое не проверяет и , одновременно устанавливающие блокировку, не будет страдать от этого состояния гонки; одна атомная операция необходима для успешной блокировки без состояния гонки.

Поскольку блокировки APC будут существовать только в контексте выполнения php, это, вероятно, не лучшее решение для общей блокировки, поскольку оно не поддерживает блокировки между хостами. Memcache также предоставляет функцию атомарного добавления и, следовательно, может также использоваться с этим методом, который является одним из методов блокировки между хостами. Redis также поддерживает элементарные функции SETNX и TTL и является очень распространенным методом блокировки и синхронизации между хостами. Однако ОП запрашивает решение для APC, в частности.

5 голосов
/ 02 декабря 2008

Если цель блокировки состоит в том, чтобы не допустить попытки нескольких процессов заполнить пустой ключ кэша, почему вы не хотите иметь блокирующую блокировку?


  $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);
   }

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

3 голосов
/ 02 декабря 2008

На самом деле, проверьте, будет ли это работать лучше, чем совет Питера.

http://us2.php.net/flock

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

3 голосов
/ 01 декабря 2008

Если вы не возражаете против блокировки вашей файловой системы, вы можете использовать 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

1 голос
/ 30 января 2010

Я понимаю, что это год, но я просто наткнулся на этот вопрос, проводя некоторые исследования по блокировке в 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, например ::10000

function key_for_lock($str) {
    return md5($str."locked");
}

Параметр $expire - это хорошая функция APC, так как он предотвращает вечную блокировку, если ваш скрипт умирает или что-то в этом роде.

Надеюсь, этот ответ будет полезен для всех, кто спотыкается здесь год спустя.

0 голосов
/ 05 января 2017

APC теперь считается необслуживаемым и мертвым . Его преемник APCu предлагает блокировку через apcu_entry. Но имейте в виду, что он также запрещает одновременное выполнение любых других функций APCu. В зависимости от вашего варианта использования, это может быть хорошо для вас.

Из руководства:

Примечание: Когда элемент управления вводит apcu_entry(), блокировка для кеша приобретается исключительно, она снимается, когда элемент управления покидает apcu_entry(): фактически это превращает тело generator в критическое раздел, запрещающий двум процессам выполнять одни и те же пути кода одновременно. Кроме того, он запрещает одновременное выполнение любых других функций APCu, поскольку они получат такую ​​же блокировку.

0 голосов
/ 27 апреля 2016

Не могу сказать, является ли это лучшим способом справиться с работой, но по крайней мере это удобно.

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

Третий необязательный аргумент может пригодиться, если вы используете эту функцию более одного раза для каждого файла.

И последнее, но не менее важное: нетрудно изменить эту функцию (сохранив свою подпись), чтобы использовать какой-либо другой вид механизма блокировки позднее, например если вам случится работать с несколькими серверами.

0 голосов
/ 02 мая 2011

EAccelerator имеет методы для этого; eaccelerator_lock и eaccelerator_unlock.

0 голосов
/ 03 декабря 2008

На самом деле я обнаружил, что мне вообще не нужна блокировка ... учитывая, что я пытаюсь создать карту всех ассоциаций class => path для автозагрузки, она не ' Неважно, если один процесс перезаписывает то, что нашел другой (это очень маловероятно, если его правильно закодировать), потому что в любом случае данные попадут туда. Итак, решение оказалось «без замков».

...