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
运行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调用修改成两次,代码如下:
再次运行 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 解锁
为加锁的文件句柄解锁
这样的加锁方式必然可以保证加锁程序块的原子性,但同时也牺牲了程序的效率,因此,我们实际的程序中应该在程序的加锁和解锁代码间嵌入尽量少的程序逻辑(尤其是独占锁),保证程序尽快解锁。
最后,附上加上锁机制以后的程序:
运行该程序,产生正确的结果。
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
首先说明一下我的环境是 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
相关文章推荐
- yii学习历程2-yii相关配置
- Cannot retrieve debugging output
- 利用php的explode函数将字符串按分隔符(比如空格)分拆并组装在数组中-----要考虑连续空格问题
- php 遍历一个文件夹下的所有文件和子文件夹
- PHP学习笔记——入门篇(1)——语法&变量
- 【PHP】织梦仿站学习笔记(一)
- php中的==和===
- 理解PHP中会话控制
- windows下安装zend框架
- 《从云端走下来》02:VPS主机服务器Ubuntu快速搭建LAMP+phpMyAdmin环境
- PHP 冒泡排序法
- PHP 多input file文件上传
- PHP之类型约束
- 利用ItextPdf、core-renderer-R8 来生成PDF
- laravel+php+微信扫码支付
- php中字符串常用的截取操作
- 黄聪:PHP代码性能加速-开启Zend OPcache-优化CPU
- php7做的一些改变
- ContentProvider 一个应用程序访问另一个应用程序
- PHP内核探索之变量(2)-理解引用