单脚本发起多XHR请求至同一PHP文件的会话创建问题咨询
嘿,抱歉再叨扰一次,但这次我碰到了个新疑问😅
我现在有个PHP文件是这么写的:
<?php session_start(); $_SESSION['globalVar'] = "undefined"; if (!empty($_POST['triggerLoop'])) { for ($x = 0; $x < 5; $x++) { $_SESSION['globalVar'] = getResult(); sleep(1); } } //------------------------------------------------------ //Listen if (!empty($_POST['listen'])) { // 我本来打算在这里读取 $_SESSION['globalVar'] 并返回给前端 }
情况是这样:我用前端发起了多个XHR请求,一个请求触发triggerLoop分支(这个分支会跑5秒,每一秒更新一次session里的globalVar),同时我不断发起listen请求想实时获取globalVar的最新值,但结果要么是listen请求一直卡着直到triggerLoop跑完才返回,要么只能拿到最后一次更新的值,根本看不到中间的变化。这到底是哪里出问题了呀?
哈哈,这个问题太常见了!核心问题出在PHP默认的session文件锁机制上。
当你的PHP脚本调用session_start()时,PHP会自动锁定对应的session文件——这个锁会一直持有到整个请求执行完毕,或者你主动调用session_write_close()释放它。所以你的triggerLoop请求一旦启动,就会攥着session锁跑满5秒,期间所有的listen请求都得排队等这个锁释放才能读取session数据,自然就看不到中间的更新,甚至会被阻塞很久。
给你几个实用的解决办法:
循环里主动释放session锁
在triggerLoop的每次循环中,更新完session值后立刻调用session_write_close()把数据写入文件并释放锁,下次要更新时再重新开启session。修改后的代码大概是这样:if (!empty($_POST['triggerLoop'])) { for ($x = 0; $x < 5; $x++) { session_start(); // 重新开启session以获取锁 $_SESSION['globalVar'] = getResult(); session_write_close(); // 写完立刻释放锁,让其他请求能读取 sleep(1); } }这样每次循环更新完就释放锁,
listen请求就能及时拿到最新的globalVar值了。换用非阻塞的session存储
如果你的项目有条件,可以把session存储换成Redis或者Memcached这类内存数据库。它们的锁机制比文件存储灵活得多,不会因为单个请求长时间占用而阻塞其他请求,而且读写速度也更快。不过这个需要你配置PHP的session驱动,稍微有点动手成本,但长期来看是更优的方案。优化listen请求的session使用
要是listen请求只需要读取session数据,那读完之后也立刻调用session_write_close(),别拿着锁不放,这样能进一步减少阻塞的概率。
另外提一句,如果getResult()函数本身有耗时操作,也会拖慢整个流程,不过这就是另一个优化方向啦。
备注:内容来源于stack exchange,提问作者Abb

