在php中获得锁的最佳方法

在php中获得锁的最佳方法,php,locking,apc,Php,Locking,Apc,我正在尝试更新APC中的一个变量,将会有很多进程尝试这样做 APC不提供锁定功能,所以我正在考虑使用其他机制。。。到目前为止,我发现的是mysql的GET_LOCK()和php的flock()。还有什么值得考虑的吗 更新:我发现了sem_acquire,但它似乎是一个阻塞锁。如果您不介意将锁基于文件系统,那么您可以使用fopen()和模式“x”。以下是一个例子: $f = fopen("lockFile.txt", 'x'); if($f) { $me = getmypid();

我正在尝试更新APC中的一个变量,将会有很多进程尝试这样做

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


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

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

事实上,检查一下这是否比Peter的建议更有效


使用独占锁,如果您对它感到满意,则将尝试锁定文件的所有其他内容都置于2-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);
   }

如果缓存是好的,你只需使用它。如果缓存中什么都没有,你会得到一个锁。一旦你有了锁,你需要仔细检查缓存,以确保在你等待获得锁时,缓存没有被重新填充。如果缓存已重新填充,请使用该值并释放锁,否则,您将执行计算,填充缓存并释放锁。

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

我意识到这已经是一年前的事了,但我只是在自己研究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的一个很好的使用特性,因为它可以防止在脚本死亡或类似情况下永久保留锁

希望这个答案能对一年后在这里遇到困难的其他人有所帮助。

有解决方法
eaccelerator\u lock
eaccelerator\u unlock

您可以使用该功能实现这一点,而无需求助于文件系统或mysql
apc_add
仅在变量尚未存储时成功;因此,提供了一种锁定机制。TTL可用于确保有故障的锁夹不会永远保持锁的状态

/*
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\u add
仅在尚未设置值的情况下设置该值(“将其添加”到缓存中),因此它确保两个调用不能同时获得锁,而不管它们在时间上是否接近。不同时检查和设置锁的任何解决方案都不会固有地受到这种竞争条件的影响;在没有竞争条件的情况下成功锁定需要一个原子操作


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

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

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\u entry()
时,缓存的锁以独占方式获取,当控件离开
apcu\u entry()
时,它被释放:实际上,这将
生成器的主体变成一个关键部分,禁止两个进程同时执行相同的代码路径。此外,它禁止并发执行任何其他APCu函数,因为它们将获得相同的锁


变量究竟包含什么;你为什么担心锁?你也许可以解决这个问题。一个(晚)字的警告:MySQL GET_LOCK()有一个非常危险的行为。第二个GET_LOCK()悄悄地释放同一连接上的前一个锁。MySQL每个连接只能持有一个锁。使用股票MySQL不可能使用嵌套锁。在MySQL 5.7中,get_lock解决了上述问题,因此您可以像现在期望的那样使用它:只要您不需要NFS,这可能是最简单的解决方案。虽然在释放羊群之前,如果锁定脚本崩溃,很有可能会出现竞态或更糟的堆积。是的,如果脚本崩溃,你可以得到堆积,但有办法解决这个问题,或者至少