Python进程陷入死循环之后怎么办?这种情况下单纯分析代码很难快速定位到死循环发生的位置。如果能获取到进程当前的调用栈,那么问题就容易解决了。
本地处理策略
如果是在本地开发环境遇到这个问题,那么最为简便的方式就是使用PyCharm的Attach to Local Process这一功能。
在PyCharm菜单栏里Run/Attach to Local Process,然后选择本地Python进程。连接上之后暂停进程,PyCharm里面就会直接定位到最后在执行中的语句。于是就很方便的知道问题的出处了。
服务端处理策略
gdb
可惜,通常遇到这种问题都不是在本地。服务端遇上这个问题的话,相对就麻烦不少。DebuggingWithGdb上介绍了使用gdb来调试的方法,
安装必要的依赖之后,
apt-get install gdb python2.7-dbg
可以用gdb attach上进程,之后再用py-系列命令去获取信息与调试。之前某次遇到问题时,尝试使用此方法未能成功,不过好像又可以了,估计是当时环境没弄正确..
除了gdb之外,还有没其它方法?
signal
Python的sys模块中_current_frames可以获取到当前各个线程的frame,通过frame可以获取到thread当前调用栈,
def output_stacks():
for tid, frame in sys._current_frames().iteritems():
print 'thread: ', tid
print ''.join(traceback.format_list(traceback.extract_stack(frame)))
print inspect.getargvalues(frame)
因此只要有机会能够执行这个方法进行输出,也就能定位到死循环发生的位置。
这种需求下可以考虑使用signal,通过定义signal处理函数来让进程按既定方式输出内容,
import signal
def signal_handler(signum, frame):
output_stacks()
signal.signal(signal.SIGUSR1, signal_handler)
在命令行下执行,
kill -30 pid
来给进程发送指定信号,对应进程就可以接收该信号并进行处理。
RPyC
上面两种方式需要登录到服务器上进行处理,如果在本地能远程获取到如上信息就更为方便。在这个思路下,服务端在启动的时候就需要提供能够远程访问的服务。RPyC是其中一种方式。服务端可以启动一个线程用于监听RPyC服务,
import threading
from rpyc.core import SlaveService
from rpyc.utils.server import ThreadedServer
class RPyCThread(threading.Thread):
def __init__(self, port):
super(RPyCThread, self).__init__()
self.port = port
def run(self):
server = ThreadedServer(SlaveService, port=self.port, reuse_addr=True)
server.start()
在本地通过RPyC进行连接并调用相应函数来获取输出,
修改output_stacks让其不直接输出,而是将结果返回,
def output_stacks():
result = []
for tid, frame in sys._current_frames().iteritems():
result.append(
(tid, ''.join(traceback.format_list(traceback.extract_stack(frame))), inspect.getargvalues(frame))
)
return result
在本地按照之前记录过的方式进行处理,
connect(ip, port)
from xxx import output_stacks
print output_stacks()
如此就能快速在本地获取到服务端进程中的调用栈了。