PHP 进程锁定问题
今天看了一个 PHP 进程锁定的文章,
http://www.cnblogs.com/sunli/archive/2009/11/24/1609553.html
刚好,前几天也写了一个 进程锁定的类,这个类要比上面的文章里面提到的更加强大一写。
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 ;
}
}
}
?>
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 ;
?>
/* *
* 进行写锁定的测试
* 打开线程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 消息队列中 http://www.cnblogs.com/niniwzw/archive/2009/11/17/1604677.html
就可以实现 线程安全的消息队列。