您的位置:首页 > 编程语言 > PHP开发

PHP程序的原子性和PHP的文件锁

2016-02-25 23:24 666 查看
原文:http://www.tuicool.com/articles/QfmaMv

首先说明一下我的环境是 Nginx + spawn-cgi。spawn-cgi启动了64个php-cgi,端口为9000(./spawn-cgi -p 9000 -F 64 -f ~/php-cgi)

接下来假如我们有这样一个需求:每次用户输入一串数据,我们将用户的信息以及这串数据记录到文本当中。程序如下:

test.php

<?php
$usrinfo = isset($_GET["usrinfo"])?$_GET["usrinfo"]:exit(1);
$stinfo = isset($_GET["stinfo"])?$_GET["stinfo"]:exit(1);
echo $stinfo;
$pid = posix_getpid();
$fp = fopen(“usrinfo.txt”,”a+”);
fwrite($fp,”user:”.$usrinfo.” stinfo:”.$stinfo.”–”.$pid.”\n”);
fclose($fp);


运行ab命令:ab -n 1000 -c 100 “http://localhost:9080/test.php”(ab是apache自带的网络测试工具-n 1000表示共发起1000次请求,-c 100表示模拟每次100个请求),查看usrinfo.txt文件结果,文件输出 -

user:usr_test stinfo:st_test–22311–47947

user:usr_test stinfo:st_test–22346–2782

user:usr_test stinfo:st_test–22285–55237

user:usr_test stinfo:st_test–22305–63849

user:usr_test stinfo:st_test–22288–9748

user:usr_test stinfo:st_test–22296–37314

……………………………………………

…………………………………………….

user:usr_test stinfo:st_test–22311–37033

………………………………………………

——————————————-

user:usr_test stinfo:st_test–22346–16242

………………………………………………..

/* 以上结果共1000行 */

由于我们的ab测试发起1000次请求,所以最终结果是1000行,并且fastcgi维护的是一个装有php-cgi的进程池,使php-cgi解析了一次php程序作业以后不自动销毁而是再次解析其他的php程序作业。因此有如图所示的22311号进程处理完毕一个作业以后再次处理其他作业。

通过结果我们看到,我们程序输出的数据是正确的。而没有出现由于fastcgi并发执行而导致的写操作乱序现象。事实上,PHP的fwrite函数调用了C的fwrite函数(缓冲写),C语言的fwrite函数调用了Linux系统调用write,而无论fwrite或者write函数都是原子操作,是不可分割的,所以上述的代码段可以正常运行。

如果我们将一次fwrite调用修改成两次,代码如下:

<?php
$usrinfo = isset($_GET["usrinfo"])?$_GET["usrinfo"]:exit(1);
$stinfo = isset($_GET["stinfo"])?$_GET["stinfo"]:exit(1);
echo $stinfo;
$pid = posix_getpid();
$fp = fopen(“usrinfo.txt”,”a+”);
$num = rand(0,100000);
fwrite($fp,”user:”.$usrinfo.” stinfo:”.$stinfo.”–”.$pid.”–”.$num.”\n”);
fwrite($fp,”talking 1 — pid:$pid and num:$num\n”);
fclose($fp);


再次运行 ab -n 1000 -c 100 “http://localhost:9080/test.php”,则再次打开usrinfo.txt

user:usr_test stinfo:st_test–22319–96796

user:usr_test stinfo:st_test–22303–79356

talking 1 — pid:22319 and num:96796

talking 1 — pid:22303 and num:79356

user:usr_test stinfo:st_test–22293–23359

user:usr_test stinfo:st_test–22309–48593

talking 1 — pid:22293 and num:23359

talking 1 — pid:22309 and num:48593

user:usr_test stinfo:st_test–22338–22198

user:usr_test stinfo:st_test–22316–75875

talking 1 — pid:22338 and num:22198

talking 1 — pid:22316 and num:75875

……………………………………………………………………………………..

我们看到,程序的写顺序已经被完全破坏,如果我们把spawn-cgi开启php-cgi的数减少到1(./spawn-cgi -p 9000 -F 1 -f ~/php-cgi),则该程序结果运行正常,而如果这样,php-cgi就变成了单进程了,而-cgi开启的php-cgi越多,该程序的结果就会越混乱。

其实,很多时候,我们并没有考虑我们php代码的并行能力,尤其是在我们的php代码对某个资源可读可写的时候。但这并不是说php的所有操作就都是原子的,事务的,可并行的。由于我们的php脚本是运行在fastcgi容器中,而fastcgi是多进程的,所以如果php程序访问了临界资源,势必造成程序结果的不正确性。

解决问题的办法是使用锁机制。php没有继承posix标准支持的unix锁:比如记录锁fcntl,线程锁等,而只封装了一个linux系统调用flock(信号量也能做成锁,这个以后会给大家分享用法),flock形式为flock(fp,fp,type),其中fp为文件句柄,而fp为文件句柄,而type为

/* 当一个文件的打开方式是可读可写的,通常需要向文件加入锁机制 */

LOCK_SH 共享锁:

通常为进程向文件请求读操作时需加共享锁。共享锁可支持任意个进程间的读操作,如果写一个加了共享锁的文件则进程阻塞进入SLEEP状态值到共享锁解锁

LOCK_EX 独占锁:

通常为进程向文件的写操作加独占锁,一旦文件加上了该锁,则其他任意进程访问该文件时都会阻塞,直到解锁为止。

LOCK_UN 解锁

为加锁的文件句柄解锁

这样的加锁方式必然可以保证加锁程序块的原子性,但同时也牺牲了程序的效率,因此,我们实际的程序中应该在程序的加锁和解锁代码间嵌入尽量少的程序逻辑(尤其是独占锁),保证程序尽快解锁。

最后,附上加上锁机制以后的程序:

<?php
$usrinfo = isset($_GET["usrinfo"])?$_GET["usrinfo"]:exit(1);
$stinfo = isset($_GET["stinfo"])?$_GET["stinfo"]:exit(1);
echo $stinfo;
$pid = posix_getpid();
$fp = fopen(“usrinfo.txt”,”a+”);
$num = rand(0,100000);
flock($fp,LOCK_EX);
fwrite($fp,”user:”.$usrinfo.” stinfo:”.$stinfo.”–”.$pid.”–”.$num.”\n”);
fwrite($fp,”talking 1 — pid:$pid and num:$num\n”);
flock($fp,LOCK_UN);
fclose($fp);


运行该程序,产生正确的结果。

user:usr_test stinfo:st_test–22301–63987

talking 1 — pid:22301 and num:63987

user:usr_test stinfo:st_test–22323–81895

talking 1 — pid:22323 and num:81895

user:usr_test stinfo:st_test–22302–66557

talking 1 — pid:22302 and num:66557

user:usr_test stinfo:st_test–22282–82967

talking 1 — pid:22282 and num:82967

user:usr_test stinfo:st_test–22333–6534

talking 1 — pid:22333 and num:6534
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: