背景
今天在模拟一个导出进度条时, 需要在session中存放当前的导出进度,为了模拟进度,使用sleep增加导出时间,设置导出进度, 然后前端间隔时间去获取进度,但是此时获取进度的所有请求会阻塞,知道sleep函数执行完,获取进度的请求才会返回
原因
默认使用的php的session模式是file模式,也就说会把session存储的值写入到服务器的某个目录下的文件中,文件名是使用hash函数对用户的浏览 器+ip等获取的结果(同一电脑浏览器多次访问,这个值不变),在A请求中,写入session的初始值,此时A请
求占领该文件的写锁,然后A请求进入sleep阻塞状态,当b,c,d等请求(同一电脑浏览器)从session获取进度时,因为session所在的文件被A请求占领,PHP在访问SESSION数据时,首先需要获取到SESSION锁,否则就会sleep一段时间,然后重试。所以这些请求会一直阻塞,一直等到A请求处理完毕,其他请求才会返回。
示例有问题代码
// 前端代码
function getProgress() { // 获取进度
$.get(
"{:U('/admin/UserScore/getProgress')}",
function(data) {
if (data == 'ok') {
clearInterval(a);
}
}
);
}
var a = '';
function getFile() { // 虎丘文件
$.post(
"{:U('/admin/UserScore/getFile')}",
function(data) {
console.log('success');
}
);
a = setInterval(getProgress, 1000);
}
getFile();
// 后端代码
public function getProgress() {
$fileLength = 4
$result = $_SESSION['status'];
if ($fileLength == $result) { //获取更新进度
echo 'ok';
} else {
echo 'loading';
}
}
public function getFile() {
$_SESSION['status'] = 0; //初始化进度
for ($i=0; $i < 5; $i++) { // 模拟处理,sleep阻塞时间
sleep(2);
$_SESSION['status'] = $i;
}
}
解决办法:
- getFile函数每次写入session之后就释放写锁即可,每次写入时,在打开session,更改getFile函数如下即可
```
public function getFile() {
$_SESSION['status'] = 0; //初始化进度
session_write_close(); // 释放写锁
for ($i=0; $i < 5; $i++) { // 模拟处理,sleep阻塞时间
sleep(2);
session_start();// 开启session
$_SESSION['status'] = $i;
session_write_close();// 释放写锁
}
}
```