最近使用Python遇到两个非常不好定位的问题,表现都是Python主进程hang住。最终定位出一个是subprocess模块的问题,一个是threading.Timer线程的问题。

subprocess模块不当使用的问题

Python的subprocess比较强大,基本上能替换os.system、os.popen、commands.getstatusoutput的功能,但是在使用的过程中需要注意参数stdin/stdout/stderr使用subprocess.PIPE的情况,因为管道通常会有默认的buffer size(Linux x86_64下实测是64K,这里有个疑问io.DEFAULT_BUFFER_SIZE是8K,而ulimit -a的pipe size为512 * 8 = 4K?),父进程如果不使用communicate消耗掉子进程write pipe(stdout/stderr)中的数据,直接进入wait,此时子进程可能阻塞在了pipe的写上,从而导致父子进程都hang住。下面是测试代码。

# main.py

#!/usr/bin/env python

# encoding: utf-8

import subprocess

import os

import tempfile

import sys

import traceback

import commands

# both parent and child process will hang

# if run.py stdout/stderr exceed 64K, since

# parent process is waiting child process exit

# but child process is blocked by writing pipe

def testSubprocessCallPipe():

# call: just Popen().wait()

p = subprocess.Popen(["python", "run.py"],

stdin=subprocess.PIPE,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

ret = p.wait()

print ret

# will not hang since the parent process which

# call communicate will poll or thread to comsume

# the pipe buffer, so the child process can write

# all it's data to stdout or stderr pipe and it will

# not be blocked.

def testSubprocessCommunicate():

p = subprocess.Popen(["python", "run.py"],

stdin=subprocess.PIPE,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

print p.communicate()[0]

# will not hang since sys.stdout and sys.stderr

# don't have 64K default buffer limitation, child

# process can write all it's data to stdout or

# stderr fd and exit

def testSubprocessCallStdout():

# call: just Popen().wait()

p = subprocess.Popen(["python", "run.py"],

stdin=sys.stdin,

stdout=sys.stdout,

stderr=sys.stderr)

ret = p.wait()

print ret

# will not hang since file has no limitation of 64K

def testSubprocessCallFile():

stdout = tempfile.mktemp()

stderr = tempfile.mktemp()

print "stdout file %s" % (stdout,), "stderr file %s" % (stderr,)

stdout = open(stdout, "w")

stderr = open(stderr, "w")

p = subprocess.Popen(["python", "run.py"],

stdin=None,

stdout=stdout,

stderr=stderr)

ret = p.wait()

print ret

print os.getpid()

# not hang

print "use file"

testSubprocessCallFile()

# not hang

print "use sys.stdout and sys.stderr"

testSubprocessCallStdout()

# not hang

print "use pipe and communicate"

testSubprocessCommunicate()

# hang

print "use pipe and call directly"

testSubprocessCallPipe()

# run.py

import os

print os.getpid()

string = ""

# > 64k will hang

for i in range(1024 * 64 - 4):

string = string + "c"

# flush to my stdout which might

# be sys.stdout/pipe/fd...

print string

另外,在subprocess模块源码中还注释说明了另外一种由于fork -> 子进程gc -> exec导致的进程hang住,详细信息可以阅读subprocess模块源码。

threading.Timer的使用不当的问题

定位步骤:

pstack 主进程,查看python语言源码的c调用栈,追踪主线程(图中线程1)的各个函数调用栈的python源码,猜测是阻塞在threading._shutdown方法上,修改threading模块源码,并添加日志,定位确实阻塞在_exitFunc的循环join thread上。

线程2的表现是不断创建不断退出,为threading.start入口添加打印traceback,最终定位在一个模块的心跳计时器。调大心跳周期,观察步骤1中的线程id,确定是心跳计时器线程。注: approach 2中可用ctrl-c构造异常,构造hang住的情况。

重现poc

import threading

import time

import sys

# approach 1

class TestClassA(object):

timer = None

count = 0

def __del__(self):

print "called del"

if self.timer is not None:

self.timer.cancel()

def new_timer(self):

# current reference 3 + getrefcount 1 = 4

print "in new_timer: %d" % (sys.getrefcount(self))

print "ffff"

self.count += 1

# my father timer thread exit, ref count -1, but start

# a new thread will make it still 3

self.timer = threading.Timer(1, self.new_timer)

self.timer.start()

def start_timer(self):

self.timer = threading.Timer(1, self.new_timer)

self.timer.start()

def test():

t = TestClassA()

print "enter test: %d" % (sys.getrefcount(t),) # 2

t.start_timer() # pass ref to a new timer thread through self.new_timer: 3

print "before out test: %d" % (sys.getrefcount(t),) # 3

# approach 2

class TestClassB(object):

timer = None

count = 0

def __del__(self):

print "called del"

def func(*ins):

print "fffff"

ins[0].count += 1

ins[0].timer = threading.Timer(1, func, ins) # will increase reference count of ins

ins[0].timer.start()

def test_in_scope():

t = TestClassB()

print "enter test_in_scope: %d" % (sys.getrefcount(t))

t.timer = threading.Timer(1, func, (t,))

t.timer.start()

while t.count < 4:

time.sleep(1)

#try:

# while t.count < 4:

# time.sleep(1)

#except:

# pass

# if we interrupt or raise some other exceptions and not catch that,

# will hang

t.timer.cancel()

print "before exit test_in_scope: %d" % (sys.getrefcount(t))

# approachh 3

def test_closure():

t = TestClassA()

print "enter test_closure: %d" % (sys.getrefcount(t),)

def func_inner():

print "ffffffff"

t.timer = threading.Timer(1, func_inner) # will increase reference count

t.count += 1

t.timer.start()

print "in func: %d" % (sys.getrefcount(t))

t.timer = threading.Timer(1, func_inner)

t.timer.start()

print "before out test_closure: %d" % (sys.getrefcount(t),)

#print "================= test approach 1 ==============="

#print "before test"

#test()

#print "after test"

print "================= test approach 2 ==============="

print "before test_in_scope"

test_in_scope()

print "after test_in_scope"

#print "================= test approach 3 ================"

#print "before test_closure"

#test_closure()

#print "after test_closure"

print "before exit main thread, it will wait and join all other threads"

sys.exit()

linux 服务hang住原因,Python主进程hang住的两个原因相关推荐

  1. python主进程退出时子进程也退出_主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(三)...

    之前两篇文章讨论了进程意外退出时,如何杀死子进程,这节我们研究下在使用进程池multiprocessing.Pool时,如何保证主进程意外退出,进程池中的worker进程同时退出,不产生孤儿进程.如果 ...

  2. python主进程 子进程_pool主进程捕获子进程异常

    问题背景: 主进程做任务调度,子进程做任务执行,子进程定时返回进度信息,主进程获取进度,进度为100时,子进程任务结束.子进程执行过程中如果有异常,主进程需要捕获 python多进程调度, 主进程如何 ...

  3. python主进程 子进程_Python关闭主进程时关闭子进程

    作为@tony suggested,您可以在使用multiprocessing模块创建的子进程上设置daemon=True标志.要在python2.4上安装它,请键入:pip install mult ...

  4. 转:Python 主进程被杀死时,如何保证子进程同时退出而不变为孤儿进程

    发布于博客园,作者:Tourun <主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(一)> <主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(二)> < ...

  5. Python创建进程、线程的两种方式

    代码创建进程和线程的两种方式 很多人学习python,不知道从何学起. 很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手. 很多已经做案例的人,却不知道如何去学习更加高深的知识. ...

  6. linux主进程退出时,结束子进程

    简介 主进程创建p1, p2两个进程,在主进程结束同时也让子进程结束! 代码 #include <stdio.h> #include <signal.h> #include & ...

  7. Python 之 进程

    目录 理论知识 操作系统背景知识 进程 什么是进程 进程调度 进程的并行与并发 同步异步阻塞非阻塞 同步和异步 阻塞与非阻塞 同步/异步与阻塞/非阻塞 组合 进程的创建与结束 进程的创建 进程的结束 ...

  8. linux python开发环境sql数据迁移到mysql_运用Python语言编写获取Linux基本系统信息(三):Python与数据库编程,把获取的信息存入数据库...

    运用Python语言编写获取Linux基本系统信息(三):Python与数据库编程 有关前两篇的链接: 一.实验环境: Python2.7.10.pycharm.VM虚拟机.CentOS6.3.mys ...

  9. python并行计算进程池通信_Python使用进程池管理进程和进程间通信

    与线程池类似的是,如果程序需要启动多个进程,也可以使用进程池来管理进程.程序可以通过 multiprocessing 模块的 Pool() 函数创建进程池,进程池实际上是 multiprocessin ...

最新文章

  1. WebLogic 数据源密码加密
  2. 科学家研发多模态生物识别系统,基于脑纹独特性来防范身份欺骗
  3. 【Python 必会技巧】获取字典中(多个)最大值(value)的键(key)
  4. 论文阅读 - AUTOVC: Zero-Shot Voice Style Transfer with Only Autoencoder Loss
  5. (08)System Verilog 类继承
  6. 跑步与读书都废掉了...工作目前也在换新的.
  7. linux中的bg命令作用,linux bg和fg命令
  8. JAVA、Linux部署运维常用命令
  9. 简历javaweb项目描述怎么写_JavaWeb开发简历项目经验怎么写
  10. html 表格选择滚动条,table设置tbody滚动条
  11. Day530.图灵学院之面试题④ -面经
  12. Android Things:让你陌生的面包板
  13. SpringCloud - 服务注册中心
  14. 一位二本毕业4年的java程序员
  15. linq使用Take和Skip实现分页
  16. 【附源码】计算机毕业设计SSM网上拍卖系统
  17. 游程检验.医学统计实例详解-医学统计助手★卡方检验,t检验,F检验,秩和检验,方差分析
  18. Python爬虫(7)selenium3种弹窗定位后点击操作,解决点击登录被隐藏iframe无法点击的登陆问题
  19. 基于R的Bilibili视频数据建模及分析——预处理篇
  20. 纯css3模拟龙卷风动画js特效

热门文章

  1. python是哪个人创造的文字_最早的文字是谁发明的
  2. “远程桌面出现身份验证错误,要求的函数不受支持”解决方案
  3. win7软件及游戏运行失败解决方法
  4. kali的vega安装流程
  5. 临床预测模型评鉴(PMID: 34007195)-非肥胖患者5年内发生2型糖尿病的风险
  6. 顺序容器(C++ Primer 5th)
  7. AWS CloudFront实现动静分离架构
  8. Infinity插件 让Chrome浏览器实现添加10+N个快捷方式(不限制)
  9. 免费的mac办公套件推荐:Polaris Office mac版
  10. springboot基于java的个性化推荐的电商购物商城平台设计与实现