18

我正在尝试更新 APC 中的变量,并且将有许多进程尝试这样做。

APC 不提供锁定功能,所以我正在考虑使用其他机制……我目前发现的是mysql 的GET_LOCK() 和php 的flock()。还有什么值得考虑的吗?

更新:我找到了 sem_acquire,但它似乎是一个阻塞锁。

4

11 回答 11

17
/*
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
    }
};
于 2010-10-13T10:37:50.527 回答
10

您可以使用apc_add函数来实现此目的,而无需借助文件系统或 mysql。 apc_add仅当变量尚未存储时才成功;因此,提供了一种锁定机制。TTL 可用于确保失败的锁持有者不会永远持有锁。

原因apc_add是正确的解决方案是因为它避免了在检查锁和将其设置为“由您锁定”之间存在的竞争条件。由于apc_add仅在尚未设置的情况下设置值(将其“添加”到缓存中),因此它确保了两个调用不能同时获取锁,无论它们在时间上是否接近。没有同时检查设置锁的解决方案天生就会受到这种竞争条件的影响;需要一个原子操作才能在没有竞争条件的情况下成功锁定。

由于 APC 锁只存在于该 php 执行的上下文中,因此它可能不是一般锁定的最佳解决方案,因为它不支持主机之间的锁。 Memcache还提供了原子添加功能,因此也可以与这种技术一起使用——这是主机之间锁定的一种方法。 Redis还支持原子 'SETNX' 函数和 TTL,是主机之间锁定和同步的一种非常常用的方法。但是,OP 特别要求为 APC 提供解决方案。

于 2012-10-07T03:57:52.493 回答
5

如果锁定的目的是防止多个进程尝试填充一个空的缓存键,那么为什么不想要一个阻塞锁呢?


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

如果缓存很好,你只需滚动它。如果缓存中没有任何内容,您将获得锁。获得锁后,您需要仔细检查缓存,以确保在等待获得锁时,缓存没有重新填充。如果缓存被重新填充,则使用该值并释放锁,否则,您进行计算,填充缓存然后释放锁。

于 2008-12-02T19:53:50.173 回答
3

如果您不介意将锁定基于文件系统,那么您可以将 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

于 2008-11-30T23:22:56.540 回答
3

实际上,检查一下这是否会比彼得的建议更好。

http://us2.php.net/flock

使用排他锁,如果你觉得舒服,把试图锁定文件的所有其他东西都放在 2-3 秒的睡眠中。如果操作正确,您的网站将遇到锁定资源的挂起,但不会有大量脚本争夺缓存相同的内容。

于 2008-12-02T19:26:35.160 回答
1

我意识到这已经有一年了,但我只是在自己做一些关于锁定 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 的一个很好的功能,因为它可以防止您的锁在您的脚本死亡或类似情况下被永久持有。

希望这个答案对一年后在这里绊倒的其他人有所帮助。

于 2010-01-29T22:06:49.960 回答
0

EAccelerator有它的方法;eaccelerator_lockeaccelerator_unlock

于 2011-05-02T11:14:03.340 回答
0

APC 现在被认为是无人维护和死机。它的继任者APCu通过apcu_entry. 但请注意,它还禁止同时执行任何其他 APCu 功能。根据您的用例,这对您来说可能没问题。

从手册:

注意:当控制进入时apcu_entry(),缓存的锁是独占获得的,当控制离开时释放apcu_entry():实际上,这会将主体generator变成临界区,不允许两个进程同时执行相同的代码路径。此外,它禁止任何其他 APCu 函数的并发执行,因为它们将获取相同的锁。

于 2017-01-05T09:18:13.067 回答
0

APCu 从 5.1.0 开始就有 apcu_entry,现在可以用它实现锁机制:

/** get a lock, will wait until the lock is available,
 * make sure handle deadlock yourself :p
 * 
 * useage : $lock = lock('THE_LOCK_KEY', uniqid(), 50);
 * 
 * @param $lock_key : the lock you want to get it
 * @param $lock_value : the unique value to specify lock owner
 * @param $retry_millis : wait befor retry
 * @return ['lock_key'=>$lock_key, 'lock_value'=>$lock_value]
 */
function lock($lock_key, $lock_value, $retry_millis) {
    $got_lock = false;
    while (!$got_lock) {
        $fetched_lock_value = apcu_entry($lock_key, function ($key) use ($lock_value) {
            return $lock_value;
        }, 100);
        $got_lock = ($fetched_lock_value == $lock_value);
        if (!$got_lock) usleep($retry_millis*1000);
    }
    return ['lock_key'=>$lock_key, 'lock_value'=>$lock_value];
}

/** release a lock
 * 
 * usage : unlock($lock);
 * 
 * @param $lock : return value of function lock
 */
function unlock($lock) {
    apcu_delete($lock['lock_key']);
}
于 2021-07-16T05:54:24.530 回答
0

不能说这是否是处理工作的最佳方式,但至少它很方便。

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

如果您在每个文件中多次使用此函数,则第三个可选参数会派上用场。

最后但同样重要的是,修改这个函数(同时保留其签名)以在以后使用任何其他类型的锁定机制并不难,例如,如果您碰巧发现自己在使用多个服务器。

于 2016-04-27T08:14:16.283 回答
-1

实际上,我发现我根本不需要任何锁定...鉴于我要创建的是所有类 => 用于自动加载的路径关联的映射,如果一个进程会覆盖另一个进程发现的内容(如果编码正确,这是极不可能的),因为无论如何数据最终都会到达那里。因此,解决方案原来是“无锁”。

于 2008-12-03T17:29:49.653 回答