搜索

首页  >  问答  >  正文

文件锁 - PHP 读文件怎么实现加锁

有大量文件需要处理。
用一个php进程去操作会很慢。

如何在一个进程读某一个文件的时候,把文件锁上。
不让其他进程可以再读而直接跳过,继续读其他的?

把正在读得文件rename,读完之后再rename回来,效率挺低的。如果实在没有更好的方法,就只能用这个了。
flock,测试了一下,貌似不太好用,试了一下,没实现文件的非阻塞读锁。
给不同进程分配不同的文件,不好实现。
也没数据库。就算有。用数据库做锁,貌似比rename更低效。

请问有什么更好的方式可以给文件加 读锁。因为,只需要读文件。

怪我咯怪我咯2789 天前958

全部回复(6)我来回复

  • PHP中文网

    PHP中文网2017-04-10 14:26:04

    你的问题是:
    1. 很多文件,想多进程处理,以提高效率,缩短总处理时间
    2. 这些进程只需要读文件,不需要写
    3. 对每个文件,只要有一个进程处理过它就可以了,没有多个进程都必须处理它的需求

    你的需求其实是分治,将文件分为多个组(不一定要在文件系统上新建目录),然后分而治之,这种情况不需要用锁.

    锁不是用于这种场景的,锁用于下面这种场景:

    1. 文件file.txt里面记录了user1的销售额和user2的销售额,user1+user2的销售总额
    2. 进程php1负责写入user1的数据,进程php2负责写入user2的数据,两个进程各读出销售总额显示给user1,user2
    3. user1和user2同时要求写入,真的是同时,不是前后差个几秒什么的

    建议你这样解决:
    1. 启动多个PHP进程(nohup php your_script.php your_dir &)
    2. 每个PHP进程赋予一个序号(假设4个进程,那就0,1,2,3),可以通过对进程自身的pid模运算取余数得到,也可以在启动进程的时候通过命令行传入,随你了
    3. 每个进程在处理文件前先对文件名做crc32()运算,模一下进程总数: crc32(file_name) % 4, 取模结果与此进程的序号相等就读取内容并处理,不相等就跳过

    最后:小编帮我排个版吧...

    回复
    0
  • 阿神

    阿神2017-04-10 14:26:04

    1. 如@felix021 所说,flock($res, LOCK_EX|LOCK_NB) 是有效的,请好好看文档……
    2. @卖掉内裤去上网 所说,memcached 虽然是纯内存操作,但毕竟有网络或unix domain socket开销,为了一个文件锁去启动一个Memcached未免太浪费。Linux中可以使用共享内存来做锁,请参考php手册中 shm_has_var / shm_put_var 。
    3. 如你自己说的,每进程分配一个专有文件也是可以的,并不是很麻烦,如果所有工作进程都有一个主进程fork出来就更方便了,最简单最dirty的办法是,把文件名放在主进程数组里,每次fork之前,就pop出来一个文件名……
    4. 用文件rename的方法跟@卖掉内裤去上网 说的依靠判断一个lock文件存在与否的办法开销差不多,如果你的文件数不是很多,锁抢占不频繁,可以这么做……

    除了文件锁以外,其他自行实现的锁在有锁进程意外退出时,都需要自行实现解锁机制。所以,还是推荐用文件锁,会由系统来自动释放……

    关于flock的演示

    function do_flock(){
        ob_implicit_flush(true); //关闭PHP输出缓冲
        $file = __FILE__;
        $f = fopen($file, 'r');
        $count = 0;
        while(1){
          $locked = flock($f, LOCK_NB | LOCK_EX);
          if($locked) {
            echo "GOT LOCK\n";
            sleep(10);
            flock($f, LOCK_UN);
            echo "RELEASE LOCK\n";
            break;
          } else {
            echo 'LOCKED BY OTHER, WAIT:' . ($count ++) . "\n";
            sleep(1);
          }   
        }   
    }

    测试方法:

    time curl --no-buffer "http://localhost/flock"
    //在10秒钟之内另外一个terminal里再执行相同命令

    Terminal 1 输出:

    GOT LOCK
    RELEASE LOCK
    
    real	0m10.023s
    user	0m0.008s
    sys	0m0.008s

    Terminal 2 输出:

    LOCKED BY OTHER, WAIT:0
    LOCKED BY OTHER, WAIT:1
    LOCKED BY OTHER, WAIT:2
    LOCKED BY OTHER, WAIT:3
    LOCKED BY OTHER, WAIT:4
    LOCKED BY OTHER, WAIT:5
    LOCKED BY OTHER, WAIT:6
    LOCKED BY OTHER, WAIT:7
    LOCKED BY OTHER, WAIT:8
    GOT LOCK
    RELEASE LOCK
    
    real	0m19.025s
    user	0m0.008s
    sys	0m0.008s

    Ubuntu 12.04 测试通过,没有Mac没法测,但应该没啥问题,毕竟是同根同源的,PHP源码里也只是对Win有特殊实现……

    注意,以下情况会影响输出效果

    1. 浏览器有渲染缓冲,webkit核心的浏览器大概需要额外输出4k字节的空白才会开始渲染输出
    2. 如果用FastCGI方式部署的PHP,Web服务器可能会有输出缓冲,我用的Cherokee大概也是4k左右的缓冲

    针对这些缓冲,可以在每次echo时,把内容用str_pad补齐4096字节

    回复
    0
  • 怪我咯

    怪我咯2017-04-10 14:26:04

    1. 为什么你会觉得rename效率低呢?如果一个目录下的文件不是相当多的话,这个应该不低。

    2. 你肯定没有好好看flock的文档.

    如果不希望 flock() 在锁定时堵塞,则给 operation 加上 LOCK_NB(PHP 4.0.1 以前的版本中设置为 4)。

    回复
    0
  • 巴扎黑

    巴扎黑2017-04-10 14:26:04

    用memcached实现吧。

    比如读取文件 $filename = "t.txt";

    if(!$memcached->get($filename)){
       //文件锁不存在,那么执行文件读取功能
       //首先再将文件锁住,
       $memcaced->save($filename,'1');
       $fs = fopen($filename,'r+');
       
       fclose($fs);
       //读取完毕释放文件锁
       $memcaced->delete($filename);
    }else{
       // 文件锁已经存在,跳过
       
    }

    以上是 memcaced 纯内存操作,速度会很快,根本不要考虑到性能的问题,当然还有一种方法,采用真正的文件锁,即添加一个新文件的方法控制,文件争用,但是此方法将加大IO的开销。

    回复
    0
  • 巴扎黑

    巴扎黑2017-04-10 14:26:04

    flock,测试了一下,貌似不太好用,试了一下,没实现文件的非阻塞读锁。

    我记得好像有啊

    http://php.net/manual/zh/function.flo...

    LOCK_SH 就是读取锁,加锁后,其他程序可以读取,但不能写入
    LOCK_EX 就是写入锁,加锁后,其他程序不能读也不能写
    LOCK_NB(Windows不支持) 就是非阻塞模式,得不到锁立刻返回

    我觉得这三个参数组合起来完全可以实现楼主的需求.

    回复
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-10 14:26:04

    最简单解决办法,一共俩文件,一个write,一个read。reader完了等writer close了就对调文件名。

    回复
    0
  • 取消回复