当前位置:首页 > PHP教程 > php高级应用 > 列表

php并发控制中的独占锁的例子

发布:smiling 来源: PHP粉丝网  添加日期:2018-09-19 09:52:13 浏览: 评论:0 

1.并发问题

并发大家都知道是什么情况,这里说的是并发多个请求抢占同一个资源,直接上实例吧

请求:index.php?mod=a&action=b&taskid=6

处理:

  1. $key = "a_b::".$uid.'_'.$taskid
  2. $v = $redis->get($key); 
  3. if($v == 1){ 
  4.     $redis->setex($key,10,1); 
  5.     //处理逻辑省略 

2.分析

逻辑看来还可以,结果发现数据库中写入了两个同样的请求结果,我看了记录的时间戳,天!居然是同一秒.

我用microtime(true) log一下两个请求的时间差居然相差了0.0001s,就是说$redis->setex($key,10,1);还没执行成功 第二个请求已经get到跟第一个请求一样的结果。这不就是传说中的并发抢占资源。这中情况 听过很多,在开发过程中也没刻意去模拟实验过。

3.解决

方案1:第一反应就是要给处理过程加事务(数据库是mysql innoDB),加事务的结果就是 第一个请求成功了 第二个请求会执行到后面捡查发现重了会回滚。其实mysql事务在保证数据一致性上是很ok的,但是通过回滚来保证唯一资源独占代价太大,做过mysql事务测试测同学都知道,事务中的insert是已经插进去了,回滚之后才删掉的。

方案2:还有一个选择就是php中的文件独占锁,那就是说这情况下我要新建 用户数 * 任务数的文件来实现每个请求资源的独占,如果独占资源较少的话可选的解决办法:

  1. /** 
  2.      * 加锁 
  3.      */ 
  4.     public function file_lock($filename){ 
  5.         $fp_key = sha1($filename); 
  6.         $this->fps[$fp_key] = fopen($filename'w+'); 
  7.         if($this->fps[$fp_key]){ 
  8.             return flock($this->fps[$fp_key], LOCK_EX|LOCK_NB); 
  9.         } 
  10.         return false; 
  11.     } 
  12.     /** 
  13.      * 解锁 
  14.      */ 
  15.     public function file_unlock($filename){ 
  16.         $fp_key = sha1($filename); 
  17.         if($this->fps[$fp_key] ){ 
  18.             flock($this->fps[$fp_key] , LOCK_UN); 
  19.             fclose($this->fps[$fp_key] ); 
  20.         } 
  21.     } 

方案3:发现$redis->setnx()可以提供原子操作的状态:相同的key执行setnx之后没过期或者没del,再执行会返回false。这就让两个以上的并发请求得到控制必须成功获取锁才能继续。

  1. /** 
  2.      *  加锁 
  3.      */ 
  4.     public function task_lock($taskid){ 
  5.             $expire = 2; 
  6.              $lock_key ='task_get_reward_'.$this->uid.'_'.$taskid
  7.             $lock = $this->redis->setNX($lock_key , time());//设当前时间 
  8.             if($lock){ 
  9.                 $this->redis->expire($lock_key,  $expire); //如果没执行完 2s锁失效 
  10.             } 
  11.             if(!$lock){//如果获取锁失败 检查时间 
  12.                 $time = $this->redis->get($lock_key); 
  13.                 if(time() - $time  >=  $expire){//添加时间戳判断为了避免expire执行失败导致死锁 当然可以用redis自带的事务来保证 
  14.                     $this->redis->rm($lock_key); 
  15.                 } 
  16.                 $lock =  $this->redis->setNX($lock_key , time()); 
  17.                 if($lock){ 
  18.                     $this->redis->expire($lock_key,  $expire); //如果没执行完 2s锁失效 
  19.                 } 
  20.             } 
  21.             return $lock
  22.         } 
  23.         /** 
  24.          *  解锁 
  25.          */ 
  26.         public function task_unlock($taskid){ 
  27.             $this->set_redis(); 
  28.             $lock_key = 'task_get_reward_'.$this->uid.'_'.$taskid
  29.             $this->redis->rm($lock_key); 
  30.         } 
说明下setNX 和expire 这两个操作其实可以用redis事务来保证一致性

Tags: 例子

分享到: