关键读写关键的锁定问题
在文件的读写的过程中会用到一个PHP函数flock()。
为了确保操作的有效性和完整性,可以通过锁机制将并发状态转换成穿行状态。假设一个应用场景:在存在较大并发的情况下,通过fwrite向文件尾部多次有序的写入数据,不加锁的情况下会发生什么?多次有序的写入操作相当于一个事务,我们此时需要保证这个事务的完整性。
函数flock():
语法
flock(file,lock,block)
参数 | 描述 |
---|---|
file | 必需。规定要锁定或释放的已打开的文件。 |
lock | 必需。规定要使用哪种锁定类型。 |
block | 可选。若设置为 1 或 true,则当进行锁定时阻挡其他进程。 |
说明
flock() 操作的 file 必须是一个已经打开的文件指针。
lock 参数可以是以下值之一:
- 要取得共享锁定(读取的程序),将 lock 设为 LOCK_SH(PHP 4.0.1 以前的版本设置为 1)。
- 要取得独占锁定(写入的程序),将 lock 设为 LOCK_EX(PHP 4.0.1 以前的版本中设置为 2)。
- 要释放锁定(无论共享或独占),将 lock 设为 LOCK_UN(PHP 4.0.1 以前的版本中设置为 3)。
- 如果不希望 flock() 在锁定时堵塞,则给 lock 加上 LOCK_NB(PHP 4.0.1 以前的版本中设置为 4)。
示例说明:
建立两个文件a.php和b.php
<?php //a.php $file = "temp.txt"; $fp = fopen($file,"w"); if(flock($fp,LOCK_EX)){ fwrite($fp,"abc\r\n"); sleep(10); fwrite($fp,"123\r\n"); flock($fp,LOCK_UN); } fclose($fp); ?> <?php //b.php $file = "temp.txt"; $fp = fopen($file,"r"); if(flock($fp,LOCK_SH)){ echo fread($fp,1024); flock($fp,LOCK_UN); }else{ echo "文件被独占锁锁住了。。。"; } fclose($fp); ?>
先运行a.php,然后马上运行b.php,会发现b.php要等a.php运行完成后(即写入大量数据完成后),b.php才显示a.php写入的数据。
现在将b.php修改为:
<?php $file = "temp.txt"; $fp = fopen($file,"r"); if(flock($fp,LOCK_SH|LOCK_NB)){ echo fread($fp,1024); flock($fp,LOCK_UN); }else{ echo "文件被独占锁锁住了。。。"; } fclose($fp); ?>
此时进行同样的操作,会发现b.php不等a.php写完数据(运行完)就立即显示"文件被独占锁锁住了。。。"
结论:作文件缓存时,选好相关锁,不然可能导致读取数据不完整,或重复写入数据。(file_get_contents的锁不清楚,如上操作会得到不完整的数据。)
多次同时执行,虽然都写了100行,但是事务1和事务2的数据交错写入,这并不是我们想要的结果。我们要的是事务完整的执行。此时需要锁机制flock就保证了在第一个事务执行完后再执行第二个。当某一个事务执行完flock时,因为我们在这里添加的是LOCK_EX(独占锁定),所以所有对资源的操作都会被阻塞,只有当该事务执行完后,后面的事务才会执行。
什么时候用LOCK_EX,什么时候用LOCK_SH呢?
读的时候:如果不想出现dirty数据,那么最好使用LOCK_SH共享锁。可以考虑以下三种情况:
1.如果读的时候没有加共享锁,那么其它程序要写的话都会立即些成功。如果正好读了一半,然后被其它程序给写了,那么读的后一半就有可能跟前一半对不上(前一半是修改前的,后一半是修改后的);
2.如果读的时候加上了共享锁(因为只是读,没必要使用排他锁即独占锁),这个时候,其它程序开始写,这个写程序没有使用锁,那么写程序会直接修改这个文件,也会导致前面一样的问题;
3.最理想的情况是,读的时候加共享锁LOCK_SH,写的时候也进行加独占锁LOCK_EX,这样写程序会等着读程序完成之后才进行操作,而不会出现贸然操作的情况。
写的时候:如果多个写程序不加锁,同时对文件进行操作,那么最后的数据有可能一部分是a程序写的,一部分是b程序写的。所以最理想的情况还是如上第三点:读的时候加共享锁LOCK_SH,写的时候也进行加独占锁LOCK_EX。