Подтвердить что ты не робот

Лучший способ получить блокировку в php

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

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

Обновление: я нашел sem_acquire, но он блокирует блокировку.

4b9b3361

Ответ 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, так как они будут получать один и тот же замок.