本文简答介绍在linux环境下如何利用gdb来分析卡住的程序,本文使用的Python为Cpython2.7,操作系统为Debian。

阻塞在IO

程序被卡住,很可能是程序被阻塞了,即在等待(wait)等个系统调用的结束,比如磁盘IO与网络IO、多线程,默认的情况下很多系统调用都是阻塞的。多线程的问题复杂一下,后面专门介绍。下面举一个UDP Socket的例子(run_forever_block.py):

# -*- coding: utf-8 -*-

import socket

def simple_server:

address = ('0.0.0.0', 40000)

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.bind(address)

while True:

data, addr = s.recvfrom(2048)

if not data:

print "client has exist"

break

print "received:", data, "from", addr

if __name__ == '__main__':

simple_server

这是一个简单的UDP程序,代码在(0.0.0.0, 40000)这个地址上等待接收数据,核心就是第10行的recvfrom函数,这就是一个阻塞(blocking)的系统调用,只有当读到数据之后才会返回,因此程序会卡在第10行。当然,也可以通过fcntl设置该函数为非阻塞(non-blocking)。

我们来看看阻塞的情况,运行程序,然后通过top查看这个进程的状态

可以看到这个进程的pid是26466,进程状态为S(Sleep),CPU为0.0。进程状态和CPU都暗示我们,当前进程正阻塞在某个系统调用。这个时候,有一个很好使的命令:strace,可以跟踪进程的所有系统调用,我们来看看

> ~$ strace -T -tt -e trace=all -p 26466

> Process 26466 attached

> 19:21:34.746019 recvfrom(3,

可以看到,进程卡在了recvfrom这个系统调用,对应的文件描述符(file deor)是3,其实通过recvfrom这个名字,大致也能定位问题。关于文件描述符,还有一个更实用的命令,lsof(list open file),可以看到当前进程打开的所有文件,在linux下,一切皆文件,自然也就包括了socket。

> lsof -p 26466

> COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME

>

> ...

> python 26466 xxxxxxx **3u** IPv4 221871586 0t0 UDP *:40000

从上面可以看出这个文件描述符(3U)更详细的信息,比如是IPV4 UDP,监听在40000端口等等。

使用gdb调试Python程序

上面这个例子非常的简单,简单到我们直接从系统调用就能看出问题,但是实际的情况下可能更加复杂,即无法通过系统调用直接定位到处问题的代码。这个时候就可以使用gdb了,关于如何用gdb来调试python程序,可以参考了使用gdb调试Python进程这篇文章。以上面的代码为例,首先得做好准备条件

首先,参考DebuggingWithGdb,根据自己的Linux系统安装好python-dbg,在我的平台(Debian)上即 sudo apt-get install gdb python2.7-dbg

然后,下载libpython,放在自己的HOME目录下

接下来就可以使用gdb进行分析了:gdb -p 26466

在gdb的交互式环境中,使用bt命令,看到的是C栈,意义不大,我们直接来看Python栈

> (gdb) python

> >import sys

> >sys.path.insert(0, '/home/xxx')

> >import libpython

> >end

>

>

> (gdb) py-bt

>

> Traceback (most recent call first):

> File "run_forever_block.py", line 10, in simple_server

> data, addr = s.recvfrom(2048)

>   File "run_forever_block.py", line 17, in

> simple_server

可以看到,通过py-bt就能显示出完整的python调用栈,能帮助我们定位问题。还有很多py开头的命令,具体可见libpython.py中(所有gdb.Command的子类都是一个命令),这篇文章中总结了几个常用的:

> * py-list   查看当前python应用程序上下文

> * py-bt    查看当前python应用程序调用堆栈

> * py-bt-full    查看当前python应用程序调用堆栈,并且显示每个frame的详细情况

> * py-print    查看python变量

> * py-locals   查看当前的scope的变量

> * py-up    查看上一个frame

> * py-down    查看下一个frame

死循环很令人讨厌,死循环是预期之外的无限循环。最典型的预期之内的无限循环是socketserver,进程不死,服务不止。而死循环看起来很忙(CPU100%),但是没有任何实质的作用。死循环有不同的粒度,最粗的粒度是两个进程之间的相互调用,比如RPC;其次是函数级别,较为常见的是没有边界条件的递归调用,或者在多个函数之间的相互调用;最小的粒度是在一个函数内部,某一个代码块(code block)的死循环,最为常见的就是for,while语句。在Python中,函数级别是不会造成无限循环的,如代码所示:

# -*- coding: utf-8 -*-

def func1:

func

def func:

func1

if __name__ == '__main__':

func

运行代码,很快就会抛出一个异常:RuntimeError: maximum recursion depth exceeded。显然,python内部维护了一个调用栈,限制了最大的递归深度,默认约为1000层,也可以通过 sys.setrecursionlimit(limit) 来修改最大递归深度。在Python中,虽然出现这种函数级别的死循环不会导致无限循环,但是也会占用宝贵的CPU,也是决不应该出现的。

而代码块级别的死循环,则会让CPU转到飞起,如下面的代码

# -*- coding: utf-8 -*-

def func:

while True:

pass

if __name__ == '__main__':

func

这种情况,通过看CPU还是很好定位的

从进程状态R(run),以及100%的CPU,基本上就能确定是死循环了,当然也不排除是CPU密集型,这个跟代码的具体逻辑相关。这个时候,也是可以通过gdb来看看当前调用栈的,具体的准备工作如上,这里直接给出py-bt结果

> (gdb) py-bt

> Traceback (most recent call first):

>   File "run_forever.py", line 5, in func

>   File "run_forever.py", line 8, in

> (gdb)

在《无限“递归”的python程序》一文中,提到过使用协程greenlet能产生无限循环的效果,而且看起来非常像函数递归,其表现和gdb调试结果与这里的死循环是一样的

多线程死锁

由于Python的GIL,在我们的项目中使用Python多线程的时候并不多。不过多线程死锁是一个非常普遍的问题,而且一般来说又是一个低概率的事情,复现不容易,多出现在高并发的线上环境。这里直接使用《飘逸的python - 演示一种死锁的产生》中的代码,然后分析这个死锁的多线程程序

#coding=utf-8

import time

import threading

class Account:

def __init__(self, _id, balance, lock):

self.id = _id

self.balance = balance

self.lock = lock

def withdraw(self, amount):

self.balance -= amount

def deposit(self, amount):

self.balance += amount

def transfer(_from, to, amount):

if _from.lock.acquire:#锁住自己的账户

_from.withdraw(amount)

time.sleep(1)#让交易时间变长,2个交易线程时间上重叠,有足够时间来产生死锁

print 'wait for lock...'

if to.lock.acquire:#锁住对方的账户

to.deposit(amount)

to.lock.release

_from.lock.release

print 'finish...'

a = Account('a',1000, threading.Lock)

b = Account('b',1000, threading.Lock)

threading.Thread(target = transfer, args = (a, b, 100)).start

threading.Thread(target = transfer, args = (b, a, 200)).start

线程死锁代码实例

运行代码,可以看见,该进程(进程编号26143)也是出于Sleep状态,因为本质上进程也是阻塞在了某个系统调用,因此,同样可以使用strace

> p$ strace -T -tt -e trace=all -p 26143

> Process 26143 attached

> 19:29:29.289042 futex(0x1286060, FUTEX_WAIT_PRIVATE, 0, NULL

可以看见,进程阻塞在futex这个系统调用,参数的意义可以参见manpage。

gdb也非常适用于调试多线程程序,对于多线程,有几个很使用的命名

info thread:列出所有的线程,以及所在线程

thread x:切换到第X号线程

thread apply all bt:打印所有线程的调用栈

下面是简化后的结果

> (gdb) info thread

> Id Target Id Frame

> 3 Thread 0x7fb6b2119700 (LWP 26144) "python" 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> 2 Thread 0x7fb6b1918700 (LWP 26145) "python" 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> * 1 Thread 0x7fb6b3cf8700 (LWP 26143) "python" 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> (gdb) thread apply all bt

>

> Thread 3 (Thread 0x7fb6b2119700 (LWP 26144)):

> #0 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> #1 0x000000000057bd20 in PyThread_acquire_lock (waitflag=1, lock=0x126c2f0) at ../Python/thread_pthread.h:324

> #2 lock_PyThread_acquire_lock.lto_priv.2551 at ../Modules/threadmodule.c:52

> ...

>

> Thread 2 (Thread 0x7fb6b1918700 (LWP 26145)):

> #0 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> #1 0x000000000057bd20 in PyThread_acquire_lock (waitflag=1, lock=0x131b3d0) at ../Python/thread_pthread.h:324

> ...

>

> Thread 1 (Thread 0x7fb6b3cf8700 (LWP 26143)):

> #0 0x00007fb6b38d8050 in sem_wait from /lib/x86_64-linux-gnu/libpthread.so.0

> #1 0x000000000057bd20 in PyThread_acquire_lock (waitflag=1, lock=0x1286060) at ../Python/thread_pthread.h:324

> #2 lock_PyThread_acquire_lock.lto_priv.2551 at ../Modules/threadmodule.c:52

> ...

这里推荐一篇非常不错的文章,用gdb调试python多线程代码-记一次死锁的发现,记录了一个在真实环境中遇到的多线程死锁问题,感兴趣的同学可以细读。

Coredump

当进程被卡主,我们需要尽快恢复服务,被卡主的原因可能是偶然的,也有可能是必然的,而且实际中导致进程卡主的真正原因不一定那么清晰明了。在我们分析卡主的具体原因的时候,我们可能需要先尽快重启服务,这个时候就需要保留现场了,那就是coredump。

按照我的经验,有两种方式。

第一种,kill -11 pid,11代表信号SIGSEGV,在kill这个进程的同时产生coredump,这样就可以迅速重启程序,然后慢慢分析

第二种,在gdb中使用gcore(generate-core-file),这个也很有用,一些bug我们是可以通过gdb线上修复的,但问题还是需要时候继续查看,这个时候就可以用这个命令先产生coredump,然后退出gdb,让修复后的程序继续执行。

总结

当一个进程不再响应新的请求时,首先看日志,很多时候日志里面会包含足够的信息。

其次,看进程的信息,Sleep状态,以及100%的CPU都能给我们很多信息,也可以用pidstat查看进程的各种信息

如果怀疑进程被阻塞了,那么可以使用strace确认

linux下,gdb是很好的调试武器,建议平时多试试,coredump也是一定会遇到的

linux下运行的Python程序,可以配合使用python-dbg和libpython分析程序。

references

DebuggingWithGdb

GDB: The GNU Project Debugger

libpython.py

EasierPythonDebugging

用GDB调试程序

使用gdb调试Python进程

用gdb调试python多线程代码-记一次死锁的发现

原文:https://www.cnblogs.com/xybaby/p/8025435.html

python运行程序为什么会卡住_为什么我的 Python 程序卡住啦!相关推荐

  1. 运行python脚本卡住_为什么我的 Python 程序卡住啦!

    原标题:为什么我的 Python 程序卡住啦! 文 | xybaby出处 | cnblogs 服务器程序员最怕的就是程序crash,不过有时候程序没有crash,但是"不工作"了也 ...

  2. 简述python程序的基本构成_(一)Python入门-2编程基本概念:01程序的构成

    一:Python程序的构成 Python程序由模块组成.一个模块对应 一个python源文件,一般后缀名是:.py. 模块由语句组成.运行 Python 程序时,按照模块中语句的顺序依次执行. 语句是 ...

  3. python爬虫软件安装不了_为编写网络爬虫程序安装Pytho

    一. 下载Python3.5.1安装包 1.进入python官网,点击menu->downloads,网址:Download Python 2. 根据系统选择32位还是64位,这里下载的可执行e ...

  4. python语言程序设计基础上海交通大学_北京交通大学:Python语言程序设计

    『课程目录』:$ y0 q8 G3 Q" Z% p6 K│ ├─第一章概述, c) e/ m) X# s- B, r│ │ 1.1.1第1课时计算机起源 – 计算机发展史中三位里程碑人物,快 ...

  5. 2019python程序员月薪多少_最新 | 2019年Python工程师的平均薪资是多少?

    03 Python的应用范围 Python的发展方向:数据分析.人工智能.web开发.测试.运维.web安全.游戏制作等等. Python是人工智能的未来.因为考虑到语言的灵活性,其速度以及提供的机器 ...

  6. python海伦公式求三角形面积程序流程图_《求三角形面积程序代码实现》教学设计...

    一.教学目标: 1 .能说出能否构成三角形的条件,并了解其 VB 表达式:能读懂求解三角形面积的程序流程图: 2 .了解编写程序代码的一般步骤,了解变量定义, IF 语句的基本语法格式: 3 .掌握 ...

  7. 微信小程序在url简化_简化对Web应用程序的数据分析

    微信小程序在url简化 数据(Analytics / ML)到应用程序-零痛苦 (Data (Analytics/ML) to App - with zero pain) We all want to ...

  8. python以运行效率高著称吗_几个提升Python运行效率的方法之间的对比

    在我看来,python社区分为了三个流派,分别是python 2.x组织,3.x组织和PyPy组织.这个分类基本上可以归根于类库的兼容性和速度.这篇文章将聚焦于一些通用代码的优化技巧以及编译成C后性能 ...

  9. python开发面试问题及答案_前50个Python面试问题(最受欢迎)

    热门Python面试问答 下面列出的是关于Python编程语言的最常见面试问题和答案. 让我们探索!! #1)Python可以用于Web客户端和Web服务器端编程吗?哪一个最适合Python? 答案: ...

最新文章

  1. 列联表相关测量--φ列相关系数
  2. sigmoid函数(Logistic函数)
  3. 动态代码生成 静态代码生成_将速度提升到自己的个人代码生成器中
  4. 读《驯服烂代码——在编程操练中悟道》
  5. sap后台配置原因代码_【MM配置】Inventory Management 库存管理
  6. java 管程通信_Java多线程07_线程通信之管程法与信号灯法
  7. 怎么python安装mysql库_python在windows上怎么安装mysql数据库
  8. mongodb详细优化策略方案
  9. mysql中key的用法_数据库中KEY的用法
  10. python开发桌面时钟_python+PyQT实现系统桌面时钟
  11. Vuforia Area及新功能讲解
  12. 数学之美笔录(3):隐含马尔可夫模型(详解)
  13. Dialog dismiss 失效的问题,
  14. Linux音频系统编程之芯片平台适配功放Codec Driver解读
  15. 超强的苹果官网滚动文字特效实现
  16. c1-02西班牙的语言,【图片】考试的同学看过来~DELE-C1两个月准备(实用经验+超详细流程)转【西班牙语吧】_百度贴吧...
  17. OpenCV中视频操作及人脸识别案例
  18. 人体红外传感器HC-SR501
  19. Java基础系列【导读】
  20. 基于Spark的FPGrowth(关联规则算法)

热门文章

  1. 服务器机房空调维修,一份来自空调机房的维修方案?老司机看了就懂
  2. 牛顿-莱布尼茨公式在程序设计中的简单运用
  3. (高效算法)求具有下列两个性质的最小自然数n: (1)n的个位数是6;(2)若将n的个位数移到其余各位数字之前,所得的新数是n的4倍。
  4. java小练习(人品计算机)
  5. 如何提高提测版本质量?
  6. centos6.5 mysql 远程访问_CentOS 6.5 中安装 Mysql 5.6,并远程连接Mysql
  7. java哈夫曼树权值计算_JAVA 哈夫曼树权值求和(代码找错)
  8. 补充订单平台分站搭建VPS选择
  9. godoc安装与自动生成API文档
  10. iview 数据表格 列表内点击改变按钮状态