• 技术文章 >后端开发 >php教程

    php如何将并发加锁的示例代码

    黄舟黄舟2017-07-17 15:50:16原创1384
    CleverCode在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面CleverCode将分析一个财务支付锁的问题。

    1 没有应用锁机制

    1.1 财务支付简化版本代码

    <?php
    
    /** 
     * pay.php 
     * 
     * 支付没有应用锁
     * 
     * Copy right (c) 2016 http://blog.csdn.net/CleverCode 
     * 
     * modification history: 
     * -------------------- 
     * 2016/9/10, by CleverCode, Create 
     * 
     */  
    
    //用户支付
    function pay($userId,$money)
    {
        
        if(false == is_int($userId) || false == is_int($money))
        {
            return false;
        }  
        
        //取出总额
        $total = getUserLeftMoney($userId);
        
        //花费大于剩余
        if($money > $total)
        {
            return false;    
        }
        
        //余额
        $left = $total - $money;
        
        //更新余额
        return setUserLeftMoney($userId,$left);
    
    }
    
    //取出用户的余额
    function getUserLeftMoney($userId)
    {
        if(false == is_int($userId))
        {
            return 0;
        }
        $sql = "select account form user_account where userid = ${userId}";
        
        //$mysql = new mysql();//mysql数据库
        return $mysql->query($sql);
    }
    
    //更新用户余额
    function setUserLeftMoney($userId,$money)
    {
        if(false == is_int($userId) || false == is_int($money))
        {
            return false;
        }        
        
        $sql = "update user_account set account = ${money} where userid = ${userId}";
        
        //$mysql = new mysql();//mysql数据库
        return $mysql->execute($sql);
    }
    
    ?>

    1.2 问题分析

    如果有两个操作人(p和m),都用用户编号100账户,分别在pc和手机端同时登陆,100账户总余额有1000,p操作人花200,m操作人花300。并发过程如下。

    p操作人:

    1 取出用户的余额1000。

    2 支付后剩余 800 = 1000 - 200。

    3 更新后账户余额800。

    m操作人:

    1 取出用户余额1000。

    2 支付后剩余700 = 1000 - 300。

    3 支付后账户余额700。

    两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。

    2 加锁设计

    锁的操作一般只有两步,一 获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式

    2.1 类图设计如下


    2.2 php源码设计如下

    LockSystem.php

    <?php
    
    /** 
     * LockSystem.php 
     * 
     * php锁机制
     * 
     * Copy right (c) 2016 http://blog.csdn.net/CleverCode 
     * 
     * modification history: 
     * -------------------- 
     * 2016/9/10, by CleverCode, Create 
     * 
     */ 
    
    class LockSystem
    {
        const LOCK_TYPE_DB = 'SQLLock';
        const LOCK_TYPE_FILE = 'FileLock';
        const LOCK_TYPE_MEMCACHE = 'MemcacheLock';
        
        private $_lock = null;
        private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock');  
        
        public function construct($type, $options = array()) 
        {
            if(false == empty($type))
            {
                $this->createLock($type, $options);
            }
        }   
    
        public function createLock($type, $options=array())
        {
            if (false == in_array($type, self::$_supportLocks))
            {
                throw new Exception("not support lock of ${type}");
            }
            $this->_lock = new $type($options);
        }
        
        public function getLock($key, $timeout = ILock::EXPIRE)
        {
            if (false == $this->_lock instanceof ILock)  
            {
                throw new Exception('false == $this->_lock instanceof ILock');          
            }  
            $this->_lock->getLock($key, $timeout);   
        }
        
        public function releaseLock($key)
        {
            if (false == $this->_lock instanceof ILock)  
            {
                throw new Exception('false == $this->_lock instanceof ILock');          
            }  
            $this->_lock->releaseLock($key);         
        }   
    }
    
    interface ILock
    {
        const EXPIRE = 5;
        public function getLock($key, $timeout=self::EXPIRE);
        public function releaseLock($key);
    }
    
    class FileLock implements ILock
    {
        private $_fp;
        private $_single;
    
        public function construct($options)
        {
            if (isset($options['path']) && is_dir($options['path']))
            {
                $this->_lockPath = $options['path'].'/';
            }
            else
            {
                $this->_lockPath = '/tmp/';
            }
           
            $this->_single = isset($options['single'])?$options['single']:false;
        }
    
        public function getLock($key, $timeout=self::EXPIRE)
        {
            $startTime = Timer::getTimeStamp();
    
            $file = md5(FILE.$key);
            $this->fp = fopen($this->_lockPath.$file.'.lock', "w+");
            if (true || $this->_single)
            {
                $op = LOCK_EX + LOCK_NB;
            }
            else
            {
                $op = LOCK_EX;
            }
            if (false == flock($this->fp, $op, $a))
            {
                throw new Exception('failed');
            }
           
    	    return true;
        }
    
        public function releaseLock($key)
        {
            flock($this->fp, LOCK_UN);
            fclose($this->fp);
        }
    }
    
    class SQLLock implements ILock
    {
        public function construct($options)
        {
            $this->_db = new mysql(); 
        }
    
        public function getLock($key, $timeout=self::EXPIRE)
        {       
            $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
            $res =  $this->_db->query($sql);
            return $res;
        }
    
        public function releaseLock($key)
        {
            $sql = "SELECT RELEASE_LOCK('".$key."')";
            return $this->_db->query($sql);
        }
    }
    
    class MemcacheLock implements ILock
    {
        public function construct($options)
        {
            
            $this->memcache = new Memcache();
        }
    
        public function getLock($key, $timeout=self::EXPIRE)
        {     
            $waitime = 20000;
            $totalWaitime = 0;
            $time = $timeout*1000000;
            while ($totalWaitime < $time && false == $this->memcache->add($key, 1, $timeout)) 
            {
                usleep($waitime);
                $totalWaitime += $waitime;
            }
            if ($totalWaitime >= $time)
                throw new Exception('can not get lock for waiting '.$timeout.'s.');
    
        }
    
        public function releaseLock($key)
        {
            $this->memcache->delete($key);
        }
    }

    3 应用锁机制

    3.1 支付系统应用锁

    <?php
    
    /** 
     * pay.php 
     * 
     * 支付应用锁
     * 
     * Copy right (c) 2016 http://blog.csdn.net/CleverCode 
     * 
     * modification history: 
     * -------------------- 
     * 2016/9/10, by CleverCode, Create 
     * 
     */  
    
    //用户支付
    function pay($userId,$money)
    {
        
        if(false == is_int($userId) || false == is_int($money))
        {
            return false;
        }  
        
        try
        {
            //创建锁(推荐使用MemcacheLock)
            $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE);             
            
            //获取锁
            $lockKey = 'pay'.$userId;
            $lockSystem->getLock($lockKey,8);
            
            //取出总额
            $total = getUserLeftMoney($userId);
            
            //花费大于剩余
            if($money > $total)
            {
                $ret = false;    
            }
            else
            { 
                //余额
                $left = $total - $money;
                
                //更新余额
                $ret = setUserLeftMoney($userId,$left);
            }
            
            //释放锁
            $lockSystem->releaseLock($lockKey); 
        }
        catch (Exception $e)
        {
            //释放锁
            $lockSystem->releaseLock($lockKey);     
        }
    
    }
    
    //取出用户的余额
    function getUserLeftMoney($userId)
    {
        if(false == is_int($userId))
        {
            return 0;
        }
        $sql = "select account form user_account where userid = ${userId}";
        
        //$mysql = new mysql();//mysql数据库
        return $mysql->query($sql);
    }
    
    //更新用户余额
    function setUserLeftMoney($userId,$money)
    {
        if(false == is_int($userId) || false == is_int($money))
        {
            return false;
        }        
        
        $sql = "update user_account set account = ${money} where userid = ${userId}";
        
        //$mysql = new mysql();//mysql数据库
        return $mysql->execute($sql);
    }
    
    ?>

    3.2 锁分析

    p操作人:

    1 获取锁:pay100

    2 取出用户的余额1000。

    3 支付后剩余 800 = 1000 - 200。

    4 更新后账户余额800。

    5 释放锁:pay100

    m操作人:

    1 等待锁:pay100

    2 获取锁:pay100

    3 获取余额:800

    4 支付后剩余500 = 800 - 300。

    5 支付后账户余额500。

    6 释放锁:pay100

    两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。

    以上就是php如何将并发加锁的示例代码的详细内容,更多请关注php中文网其它相关文章!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    专题推荐:php 代码 示例
    上一篇:php如何实现固定红包以及随机红包算法详解(图) 下一篇:php延迟静态绑定是什么?延迟静态绑定实例代码详解
    大前端线上培训班

    相关文章推荐

    • 你必须了解PHP中什么是抽象类和抽象方法• 五分钟带你看PHP中的接口interface声明与应用(实例详解)• PHP中怎样完成Cookie的创建、读取和删除?• PHP中怎样完成Session的设置、获取和删除?• 带你分清类中的构造函数与析构函数

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网