best way to obtain a lock in php

后端 未结 10 1072
甜味超标
甜味超标 2020-12-07 15:10

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

APC doesn\'t provide locking functionality, so I\'m considering using other me

相关标签:
10条回答
  • 2020-12-07 15:14
    /*
    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
        }
    };
    
    0 讨论(0)
  • 2020-12-07 15:15

    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".

    0 讨论(0)
  • 2020-12-07 15:18

    You can use the apc_add function to achieve this without resorting to file systems or mysql. apc_add only 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.

    The reason apc_add is 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_add only sets the value if it's not already set ( "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 and set the lock at the same time will inherently suffer from this race condition; one atomic operation is required to successfully lock without race condition.

    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. Memcache also provides an atomic add function and thus can also be used with this technique - which is one method of locking between hosts. Redis also 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.

    0 讨论(0)
  • 2020-12-07 15:21

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

    $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

    0 讨论(0)
  • 2020-12-07 15:22

    EAccelerator has methods for it; eaccelerator_lock and eaccelerator_unlock.

    0 讨论(0)
  • 2020-12-07 15:23

    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.

    0 讨论(0)
提交回复
热议问题