在 php 中获得锁的最佳方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/325806/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:24:06  来源:igfitidea点击:

best way to obtain a lock in php

phplockingapc

提问by tpk

I'm trying to update a variable in APC, and will be many processes trying to do that.

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

APC doesn't provide locking functionality, so I'm considering using other mechanisms... what I've found so far is mysql's GET_LOCK(), and php's flock(). Anything else worth considering?

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

Update: I've found sem_acquire, but it seems to be a blocking lock.

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

回答by harry

/*
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
    }
};

回答by Daniel Farrell

You can use the apc_addfunction to achieve this without resorting to file systems or mysql. apc_addonly succeeds when the variable is not already stored; thus, providing a mechanism of locking. TTL can be used to ensure that falied lockholders won't keep on holding the lock forever.

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

The reason apc_addis the correct solution is because it avoids the race condition that would otherwise exist between checking the lock and setting it to 'locked by you'. Since apc_addonly sets the value if it's not alreadyset ( "adds" it to the cache ), it ensures that the lock can't be aquired by two calls at once, regardless of their proximity in time. No solution that doesn't check andset the lock at the same time will inherently suffer from this race condition; one atomic operation is required to successfully lock without race condition.

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

Since APC locks will only exist in the context of that php execution, it's probably not the best solution for general locking, as it doesn't support locks between hosts. Memcachealso provides an atomic add function and thus can also be used with this technique - which is one method of locking between hosts. Redisalso supports atomic 'SETNX' functions and TTL, and is a very common method of locking and synchronization between hosts. Howerver, the OP requests a solution for APC in particular.

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

回答by Sean McSomething

If the point of the lock is to prevent multiple processes from trying to populate an empty cache key, why wouldn't you want to have a blocking lock?

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


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

If the cache is good, you just roll with it. If there's nothing in the cache, you get a lock. Once you have the lock, you'll need to double-check the cache to make sure that, while you were waiting to get the lock, the cache wasn't repopulated. If the cache was repopulated, use that value & release the lock, otherwise, you do the computation, populate the cache & then release your lock.

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

回答by too much php

If you don't mind basing your lock on the filesystem, then you could use fopen() with mode 'x'. Here is an example:

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

See www.php.net/fopen

见 www.php.net/fopen

回答by David

Actually, check to see if this will work better then Peter's suggestion.

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

http://us2.php.net/flock

http://us2.php.net/flock

use an exclusive lock and if your comfortable with it, put everything else that attempted to lock the file in a 2-3 second sleep. If done right your site will experience a hang regarding the locked resource but not a horde of scripts fighting to cache the samething.

使用排他锁,如果您对它感到满意,请将其他所有试图锁定文件的内容放入 2-3 秒的睡眠中。如果处理得当,您的站点将遇到有关锁定资源的挂起,但不会出现大量脚本来缓存相同的内容。

回答by jsdalton

I realize this is a year old, but I just stumbled upon the question while doing some research myself on locking in PHP.

我意识到这是一岁了,但我只是在自己研究锁定 PHP 时偶然发现了这个问题。

It occurs to me that a solution might be possible using APC itself. Call me crazy, but this might be a workable approach:

我突然想到使用 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");
}

In practice I might throw another function in there to generate a key to use here, just to prevent collision with an existing APC key, e.g.:

在实践中,我可能会在那里抛出另一个函数来生成一个在这里使用的密钥,只是为了防止与现有的 APC 密钥发生冲突,例如:

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

The $expireparameter is a nice feature of APC to use, since it prevents your lock from being held forever if your script dies or something like that.

$expire参数是 APC 的一个很好的使用特性,因为它可以防止你的锁在你的脚本死掉或类似的情况下被永远持有。

Hopefully this answer is helpful for anyone else who stumbles here a year later.

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

回答by sanmai

Can't say if this is the best way to handle the job, but at least it is convenient.

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

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

Usage is as simple as this.

用法就是这么简单。

$result = WhileLocked(__FILE__, function () use ($that) {
    $this->doSomethingNonsimultaneously($that->getFoo());
});

Third optional argument can come handy if you use this function more than once per file.

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

Last but not least it isn't hard to modify this function (while keeping its signature) to use any other kind of locking mechanism at a later date, e.g. if you happen to find yourself working with multiple servers.

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

回答by Pascal Hofmann

APC is now considered unmaintained and dead. It's successor APCuoffers locking via apcu_entry. But be aware, that it also prohibits the concurrent execution of any other APCu functions. Depending on your use case, this might be OK for you.

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

From the manual:

从手册:

Note:When control enters apcu_entry()the lock for the cache is acquired exclusively, it is released when control leaves apcu_entry(): In effect, this turns the body of generatorinto a critical section, disallowing two processes from executing the same code paths concurrently. In addition, it prohibits the concurrent execution of any other APCu functions, since they will acquire the same lock.

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

回答by cweiske

EAcceleratorhas methods for it; eaccelerator_lockand eaccelerator_unlock.

EAccelerator有它的方法;eaccelerator_lockeaccelerator_unlock

回答by tpk

What I've found, actually, is that I don't need any locking at all... given what I'm trying to create is a map of all the class => path associations for autoload, it doesn't matter if one process overwrites what the other one has found (it's highly unlikely, if coded properly), because the data will get there eventually anyway. So, the solution turned out to be "no locks".

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