背景:对于死锁的问题,人们往往想到出现一些关于访问很缓慢,有白页现象,要是测试环境(我就真实遇到测试环境有本文谈及一样的问题)你也就重启一下PHP的php-fpm进程发现又好了,隔一段时间又出类似的问题,你会看下日志,你会发现有很多日志是“Max execution timeout of 60 seconds exceeded”,你会发现这可能是一些php的守护进程导致的,你为了解决测试环境的问题,于是觉得应该把那个php-fpm的进程数开多点,可能会好一些,于是你开多了,一直没有面对这个问题的原因,为什么呢,因为公司装PHP的是运维装的,你没有办法或时间去装一个debug版本的php,你说这个问题让运维的人来查,你觉得能查出来?So,这个问题一拖再拖,但就是没解决,但是有一天你发现磁盘满了,用du去看整体时发现满了,但是如果一个个目录去看发现并没有占用多少,也万万没有想到PHP的死锁还会导致磁盘空间占用太多,上面这种情况我就真实遇到过,后来重新reboot操作系统,磁盘又回来了,所以,我认为是一篇好文章,所以转了此文,也想说明对于PHP的扩展这方面代码质量把关需要严格,再就是PHP本身关于锁这块要弱化(除开cookie/session和cache锁外,其它能不用就不用),尽可能少用锁,这是博主一点小看法,下面言归正传。
引子:
本期我们邀请到了 云盘服务端 团队的技术达人- 徐铁成,一个隐蔽已久的PHP死锁问题被层层掘出,感谢铁成为我们带来这次畅快的体验,小伙伴们,准备好这次技术之旅了么?
---------------
发现问题
近期发现线上很多机器的磁盘空间报警, 且日志文件已经清理,但是磁盘空间没有释放。通过ps aux | grep php-cgi 发现, 很多进程的启动时间在几天到几周甚至几个月之前。我们线上的php-cgi都有最大执行次数的。一般在1天内都会重启一次。初步结论,这些cgi进程有问题。
通过lsof -p [pid] 发现, 启动时间很久的cgi进程中打开了一些日志文件句柄,并且没有关闭。这些日志文件在文件系统中已经删除了。但是句柄没关闭,导致磁盘空间没有释放。到此,磁盘空间异常的问题基本确定。是由于cgi没有关闭文件句柄造成的。
进一步分析进程, strace -p [pid], 发现所有异常的进程都阻塞与 fmutex 状态。换句话所,异常的cgi进程死锁了。进程死锁导致打开的文件句柄没有关闭,所以导致磁盘空间异常。
为什么cgi进程会死锁呢?
什么是死锁
学过操作系统的通同学,都了解多线程的概念。在多线程中访问公共资源,需要对资源加锁。访问结束后,释放锁。如果没有释放锁,那么下一个线程来获取资源的时候就会永远都无法获取资源的锁,于是这个线程死锁了。那么CGI是多线程的公共资源访问导致的死锁吗? 答案是NO。
1. CGI 是单线程进程,通过ps 就能看到。(进程状态 Sl的才是多线程进程)。
2. 即使是多线程的,死锁发生在PHP的shutdown过程中调用glibc 中time 函数的位置,不是php模块造成的。而glibc 中的time相关函数是线程安全的,不会产生死锁。
那是什么导致的死锁呢?
通过分析linux中死锁产生的机制,发现除了多线程会产生死锁外,信号处理函数同样会产生死锁。那么cgi是由于信号处理导致的死锁吗?在这之前介绍一个感念。
函数的可重入性与信号安全
函数可重入是指,无论第几次进入该函数,函数都能正常执行并返回结果。那么线程安全函数是可重入的吗?答案是NO。 线程安全函数,在第一次访问公共资源时,会获取全局锁。如果函数没有执行完成,锁还没释放,此时进程被中断。那么在中断处理函数中,再次访问该函数,就会产生死锁。那么什么样的函数才可以在中断处理函数中访问呢? 除了没有使用全局锁的函数,还有一些signal safe的系统调用可以使用。调用任何其他的非signal safe的函数都会产生不可预知的后果(比如 死锁)。 详见 man signal。在分析死锁的原因前,我们先看看cgi执行的流程,分析其中有没有产生死锁的可能。
PHP-CGI的执行流程
Glibc中的时间函数使用到了全局锁,保证函数的线程安全,但没有保证信号安全(signal safe)。经过之前的分析,我们初步怀疑死锁是由于PHP-CGI进程接收到了一个信号,然后在signal handle中执行了非signal safe的函数。主流程在中断前,正在执行glibc中的时间函数。在函数获取的锁没释放前,进入中断流程。而中断过程中又访问了glibc中的时间函数。于是导致了死锁。
PHP-CGI的执行流程,如下图所示:
进一步分析发现,所有死锁的cgi进程的sapi_global中都记录了一个错误信息
“Max execution timeout of 60 seconds exceeded”.
60s 是我们php-cgi中设置执行超时。所以我们确认了,cig在执行过程中的确产生了超时异常,然后由于longjmp进入了shutdown过程。在shutdown过程中访问了glibc中的时间函数。导致了死锁。
void zend_set_timeout(long seconds)
{
TSRMLS_FETCH();
EG(timeout_seconds) = seconds;
if(!seconds) {
return;
}
……
setitimer(ITIMER_PROF, &t_r, NULL);
signal(SIGPROF, zend_timeout); // 此处会调用zend异常处理函数
sigemptyset(&sigset);
sigaddset(&sigset, SIGPROF);
……
}
通过gdb调试发现,所有PHP-CGI都阻塞在zend_request_shutdown中。zend_request_shutdown会调用用户自定义的php脚本中实现的shutdown函数。如果CGI执行超市,那么定时器会产生SIGPROF信号使执行流程中断。如果此时脚本刚好处于调用时间函数的状态,且还没有释放锁资源。然后执行流程进入了 timeout 函数,继续跳转到zend_request_shutdown。此时如果自定义的shutdown函数中访问了时间函数。就会产生死锁。我们从代码中找到了证据:
register_shutdown_function ('SimpleWebSvc:: shutdown’);
我们在php代码中使用qalarm系统,qalarm系统会在cgi执行结束(shutdown)的时候,注入一个钩子函数,来分析cgi执行是否正常,如果不正常,则发送报警信息。而刚好qalarm的报警处理函数中访问了时间函数。于是就有一定的概率产生死锁。
结论
通过上面的分析,我们找到了cgi死锁产生的原因,是应为在signal handler中使用了非signal safe的函数,导致了死锁。
解决办法
去掉或简化qalarm注册到shutdown中的钩子函数。避免不安全的函数调用。
来自:http://www.v2gg.com/lady/shishangzixun/20140924/57266.html

计算PHP多维数组的元素总数可以使用递归或迭代方法。1.递归方法通过遍历数组并递归处理嵌套数组来计数。2.迭代方法使用栈来模拟递归,避免深度问题。3.array_walk_recursive函数也能实现,但需手动计数。

在PHP中,do-while循环的特点是保证循环体至少执行一次,然后再根据条件决定是否继续循环。1)它在条件检查之前执行循环体,适合需要确保操作至少执行一次的场景,如用户输入验证和菜单系统。2)然而,do-while循环的语法可能导致新手困惑,且可能增加不必要的性能开销。

在PHP中高效地哈希字符串可以使用以下方法:1.使用md5函数进行快速哈希,但不适合密码存储。2.使用sha256函数提高安全性。3.使用password_hash函数处理密码,提供最高安全性和便捷性。

在PHP中实现数组滑动窗口可以通过函数slidingWindow和slidingWindowAverage来完成。1.使用slidingWindow函数可以将数组分割成固定大小的子数组。2.使用slidingWindowAverage函数可以在每个窗口内计算平均值。3.对于实时数据流,可以使用ReactPHP进行异步处理和异常值检测。

PHP中的__clone方法用于在对象克隆时进行自定义操作。使用clone关键字克隆对象时,如果对象有__clone方法,会自动调用该方法,允许在克隆过程中进行定制化处理,如重置引用类型属性以确保克隆对象的独立性。

在PHP中,goto语句用于无条件跳转到程序中的特定标签。1)它可以简化复杂嵌套循环或条件语句的处理,但2)使用goto可能导致代码难以理解和维护,3)建议优先使用结构化控制语句。整体而言,goto应谨慎使用,并遵循最佳实践以确保代码的可读性和可维护性。

在PHP中,数据统计可以通过使用内置函数、自定义函数和第三方库来实现。1)使用内置函数如array_sum()和count()进行基本统计。2)编写自定义函数计算中位数等复杂统计。3)利用PHP-ML库进行高级统计分析。通过这些方法,可以高效地进行数据统计。

是的,PHP中的匿名函数是指没有名字的函数。它们可以作为参数传递给其他函数,并作为函数的返回值,使代码更加灵活和高效。使用匿名函数时需要注意作用域和性能问题。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

Atom编辑器mac版下载
最流行的的开源编辑器

SublimeText3 英文版
推荐:为Win版本,支持代码提示!

禅工作室 13.0.1
功能强大的PHP集成开发环境

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

Dreamweaver Mac版
视觉化网页开发工具