PHP 进程锁定问题分析研究

  1. 区分读锁定 和 写 锁定。

  如果每次都使用 写锁定,那么连多个进程读取一个文件也要排队,这样的效率肯定不行。

  2. 区分 阻塞 与 非 阻塞模式。

  一般来说,如果一个进程在写一个文件的时候,另外一个进程应该被阻塞,但是,很多时候,我们可以先干点别的事情,

  然后再判断一下是否有其他人在写文件,如果没有,再加入数据,这样的效率更高。

  3. 修复了 锁定文件在linux 上的bug,特别是 在 gfs 文件系统上的bug。

  代码如下:

  

复制代码 代码如下:

  <?php

  class File_Lock

  {

  private $name;

  private $handle;

  private $mode;

  function __construct($filename, $mode = 'a+b')

  {

  global $php_errormsg;

  $this->name = $filename;

  $path = dirname($this->name);

  if ($path == '.' || !is_dir($path)) {

  global $config_file_lock_path;

  $this->name = str_replace(array("/", "\\"), array("_", "_"), $this->name);

  if ($config_file_lock_path == null) {

  $this->name = dirname(__FILE__) . "/lock/" . $this->name;

  } else {

  $this->name = $config_file_lock_path . "/" . $this->name;

  }

  }

  $this->mode = $mode;

  $this->handle = @fopen($this->name, $mode);

  if ($this->handle == false) {

  throw new Exception($php_errormsg);

  }

  }

  public function close()

  {

  if ($this->handle !== null ) {

  @fclose($this->handle);

  $this->handle = null;

  }

  }

  public function __destruct()

  {

  $this->close();

  }

  public function lock($lockType, $nonBlockingLock = false)

  {

  if ($nonBlockingLock) {

  return flock($this->handle, $lockType | LOCK_NB);

  } else {

  return flock($this->handle, $lockType);

  }

  }

  public function readLock()

  {

  return $this->lock(LOCK_SH);

  }

  public function writeLock($wait = 0.1)

  {

  $startTime = microtime(true);

  $canWrite = false;

  do {

  $canWrite = flock($this->handle, LOCK_EX);

  if(!$canWrite) {

  usleep(rand(10, 1000));

  }

  } while ((!$canWrite) && ((microtime(true) - $startTime) < $wait));

  }

  /**

  * if you want't to log the number under multi-thread system,

  * please open the lock, use a+ mod. then fopen the file will not

  * destroy the data.

  *

  * this function increment a delt value , and save to the file.

  *

  * @param int $delt

  * @return int

  */

  public function increment($delt = 1)

  {

  $n = $this->get();

  $n += $delt;

  $this->set($n);

  return $n;

  }

  public function get()

  {

  fseek($this->handle, 0);

  return (int)fgets($this->handle);

  }

  public function set($value)

  {

  ftruncate($this->handle, 0);

  return fwrite($this->handle, (string)$value);

  }

  public function unlock()

  {

  if ($this->handle !== null ) {

  return flock($this->handle, LOCK_UN);

  } else {

  return true;

  }

  }

  }

  ?>

  测试代码:

  

复制代码 代码如下:

  <?php

  /**

  * 进行写锁定的测试

  * 打开线程1

  */

  require("file_lock.php");

  $lock = new File_Lock(dirname(dirname(__FILE__)) . "/FileLock.lock");

  /** 单个线程锁定的速度 1s 钟 3万次。 **/

  /** 两个线程写,两万的数据 大概要 7s 钟*/

  /** 一个线程写,一万的数据 大概要 3.9s 钟,居然两个文件同时写,要快一点*/

  /** 不进行锁定,一个进程 写大概要 2.8s 钟,加锁是有代价的。 */

  /** 不进行锁定,两个进程 分布不是很均匀,而且大多数都冲突 */

  $lock->writeLock();

  $lock->increment();

  $lock->unlock();

  while ($lock->get() < 2) {

  usleep(1000);

  }

  sleep(1);

  echo "begin to runing \n";

  $t1 = microtime(true);

  for ($i = 0; $i < 10000; $i++)

  {

  $lock->writeLock();

  $lock->increment(1);

  $lock->unlock();

  }

  $t2 = microtime(true) - $t1;

  echo $t2;

  ?>

  我增加了一个 increment 的函数,可以实现简单的线程同步,让两个进程同时执行某段代码,当然,这个有一定的误差

  这里的误差是 0.001s。

  把这个类简单的用到 前面的memcache 消息队列中就可以实现 线程安全的消息队列。