思路其实很简单:用fopen打开一个文件,然后用flock获取该文件的独占锁,当前脚本还没跑完时,会一直持有这个锁,另一个脚本尝试获取锁就会返回失败并退出,从而达到控制同一时间只有一个脚本实例在执行的效果。

封装代码如下:

/**
 * 获取文件锁
 * 一般用于命令行脚本的单例模式
 */
class FileLock {

    /**
     * 文件句柄
     * @var resource
     */
    private $fp = null;

    /**
     * 文件路径
     * @var string
     */
    private $filePath = '';

    /**
     * 构造函数
     * @param string $filename 文件名称
     */
    public function __construct($filename) {
        $tempDir = sys_get_temp_dir();
        $this->filePath = $tempDir . DIRECTORY_SEPARATOR . $filename;
    }

    /**
     * 取得文件独占锁
     */
    public function lock() {
        $this->fp = fopen($this->filePath, 'a+');
        if (!flock($this->fp, LOCK_EX | LOCK_NB, $wouldblock)) {
            if ($wouldblock) {
                throw new Exception("另一个进程已持有文件锁");
            } else {
                throw new Exception("获取文件锁失败");
            }
        }
    }

    /**
     * 释放文件锁
     */
    public function unlock() {
        flock($this->fp, LOCK_UN);
    }

    /**
     * 析构函数
     */
    public function __destruct() {
        fclose($this->fp);
    }

}

运用举例:

if (PHP_SAPI != 'cli') {
    exit("请在命令行模式下执行\n");
}

try {
    $fl = new FileLock('testfile.lock');
    $fl->lock();

    //您的业务代码
    echo "业务执行中...\n";
    sleep(10);
    echo "执行完毕。\n";

    $fl->unlock();
} catch (Exception $ex) {
    $msg = $ex->getMessage();
    echo $msg . "\n";
    exit;
}

unlock不是必须的,因为php有垃圾回收机制,使得脚本在执行结束后会自动释放锁。