利用Python+Java调用Shell脚本时的死锁陷阱详解


Posted in Python onJanuary 24, 2018

前言

最近有一项需求,要定时判断任务执行条件是否满足并触发 Spark 任务,平时编写 Spark 任务时都是封装为一个 Jar 包,然后采用 Shell 脚本形式传入所需参数执行,考虑到本次判断条件逻辑复杂,只用 Shell 脚本完成不利于开发测试,所以调研使用了 Python 和 Java 分别调用 Spark 脚本的方法。

使用版本为 Python 3.6.4 及 JDK 8

Python

主要使用 subprocess 库。Python 的 API 变动比较频繁,在 3.5 之后新增了 run 方法,这大大降低了使用难度和遇见 Bug 的概率。

subprocess.run(["ls", "-l"])
subprocess.run(["sh", "/path/to/your/script.sh", "arg1", "arg2"])

为什么说使用 run 方法可以降低遇见 Bug 的概率呢?

在没有 run 方法之前,我们一般调用其他的高级方法,即 Older high-level API,比如 call,check_all,或者直接创建 Popen 对象。因为默认的输出是 console,这时如果对 API 不熟悉或者没有仔细看 doc,想要等待子进程运行完毕并获取输出,使用了 stdout = PIPE 再加上 wait 的话,当输出内容很多时会导致 Buffer 写满,进程就一直等待读取,形成死锁。在一次将 Spark 的 log 输出到 console 时,就遇到了这种奇怪的现象,下边的脚本可以模拟:

# a.sh
for i in {0..9999}; do
 echo '***************************************************'
done
p = subprocess.Popen(['sh', 'a.sh'], stdout=subprocess.PIPE)
p.wait()

而 call 则在方法内部直接调用了 wait 产生相同的效果。

要避免死锁,则必须在 wait 方法调用之前自行处理掉输入输出,或者使用推荐的 communicate 方法。 communicate 方法是在内部生成了读取线程分别读取 stdout stderr,从而避免了 Buffer 写满。而之前提到的新的 run 方法,就是在内部调用了 communicate。

stdout, stderr = process.communicate(input, timeout=timeout)

Java

说完了 Python,Java 就简单多了。

Java 一般使用 Runtime.getRuntime().exec() 或者 ProcessBuilder 调用外部脚本:

Process p = Runtime.getRuntime().exec(new String[]{"ls", "-al"});
Scanner sc = new Scanner(p.getInputStream());
while (sc.hasNextLine()) {
 System.out.println(sc.nextLine());
}
// or
Process p = new ProcessBuilder("sh", "a.sh").start(); 
p.waitFor(); // dead lock

需要注意的是:这里 stream 的方向是相对于主程序的,所以 getInputStream() 就是子进程的输出,而 getOutputStream() 是子进程的输入。

基于同样的 Buffer 原因,假如调用了 waitFor 方法等待子进程执行完毕而没有及时处理输出的话,就会造成死锁。
由于 Java API 很少变动,所以没有像 Python 那样提供新的 run 方法,但是开源社区也给出了自己的方案,如commons exec,或 http://www.baeldung.com/run-shell-command-in-java,或 alvin alexander 给出的方案(虽然不完整)。

// commons exec,要想获取输出的话,相比 python 来说要复杂一些
CommandLine commandLine = CommandLine.parse("sh a.sh");
  
ByteArrayOutputStream out = new ByteArrayOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(out);
  
Executor executor = new DefaultExecutor();
executor.setStreamHandler(streamHandler);
executor.execute(commandLine);
  
String output = new String(out.toByteArray());

但其中的思想和 Python 都是统一的,就是在后台开启新线程读取子进程的输出,防止 Buffer 写满。

另一个统一思想的地方就是,都推荐使用数组或 list 将输入的 shell 命令分隔成多段,这样的话就由系统来处理空格等特殊字符问题。

参考:

https://dcreager.net/2009/08/06/subprocess-communicate-drawbacks/ https://alvinalexander.com/java/java-exec-processbuilder-process-1 https://www.javaworld.com/article/2071275/core-java/when-runtime-exec—won-t.html

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
详解Python中使用base64模块来处理base64编码的方法
Jul 01 Python
python实现数独游戏 java简单实现数独游戏
Mar 30 Python
Python pymongo模块用法示例
Mar 31 Python
python 处理dataframe中的时间字段方法
Apr 10 Python
pandas 对每一列数据进行标准化的方法
Jun 09 Python
更改Python的pip install 默认安装依赖路径方法详解
Oct 27 Python
Linux CentOS Python开发环境搭建教程
Nov 28 Python
vscode 配置 python3开发环境的方法
Sep 19 Python
Django 实现Admin自动填充当前用户的示例代码
Nov 18 Python
Python通过正则库爬取淘宝商品信息代码实例
Mar 02 Python
python新式类和经典类的区别实例分析
Mar 23 Python
python基于socket函数实现端口扫描
May 28 Python
python做量化投资系列之比特币初始配置
Jan 23 #Python
python在非root权限下的安装方法
Jan 23 #Python
Python解析命令行读取参数--argparse模块使用方法
Jan 23 #Python
Python 查看文件的读写权限方法
Jan 23 #Python
Python3 中文文件读写方法
Jan 23 #Python
Python3之文件读写操作的实例讲解
Jan 23 #Python
Python实现邮件的批量发送的示例代码
Jan 23 #Python
You might like
php上传中文文件名乱码问题处理方案
2015/02/03 PHP
php数组函数array_key_exists()小结
2015/12/10 PHP
PHP面向对象之领域模型+数据映射器实例(分析)
2017/06/21 PHP
javascript 写类方式之九
2009/07/05 Javascript
jQuery中add实现同时选择两个id对象
2010/10/22 Javascript
基于jQuery的一个扩展form序列化到json对象
2010/12/09 Javascript
javascript调试过程中找不到哪里出错的可能原因
2013/12/16 Javascript
js动态创建标签示例代码
2014/06/09 Javascript
JavaScript实现的一个计算数字步数的算法分享
2014/12/06 Javascript
easyui Draggable组件实现拖动效果
2015/08/19 Javascript
JavaScript实现瀑布流布局
2020/06/28 Javascript
探讨JavaScript语句的执行过程
2016/01/28 Javascript
JS中静态页面实现微信分享功能
2017/02/06 Javascript
JavaScript引用类型Function实例详解
2018/08/09 Javascript
Vue CLI3 开启gzip压缩文件的方式
2018/09/30 Javascript
详解mpvue小程序中怎么引入iconfont字体图标
2018/10/01 Javascript
antd组件Upload实现自己上传的实现示例
2018/12/18 Javascript
关于layui 下拉列表的change事件详解
2019/09/20 Javascript
JavaScript装箱及拆箱boxing及unBoxing用法解析
2020/06/15 Javascript
python 环境变量和import模块导入方法(详解)
2017/07/11 Python
redis之django-redis的简单缓存使用
2018/06/07 Python
浅谈django的render函数的参数问题
2018/10/16 Python
Python日期时间Time模块实例详解
2019/04/15 Python
python3 requests库实现多图片爬取教程
2019/12/18 Python
python访问hdfs的操作
2020/06/06 Python
如何利用python进行时间序列分析
2020/08/04 Python
Python pymysql模块安装并操作过程解析
2020/10/13 Python
html5小技巧之通过document.head获取head元素
2014/06/04 HTML / CSS
定义一结构体变量,用其表示点坐标,并输入两点坐标,求两点之间的距离
2015/08/17 面试题
与UNIX有关的几个名词
2015/09/17 面试题
药学专业个人的自我评价
2013/12/31 职场文书
中华在我心中演讲稿
2014/09/13 职场文书
授权委托书
2014/09/17 职场文书
小学作文指导之如何写人?
2019/07/08 职场文书
pdf论文中python画的图Type 3 fonts字体不兼容的解决方案
2021/04/24 Python
科学家测试在太空中培育人造肉,用于未来太空旅行
2022/04/29 数码科技