利用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服务器拒绝服务攻击代码
Jan 16 Python
PyQt 线程类 QThread使用详解
Jul 16 Python
python多线程下信号处理程序示例
May 31 Python
在Python中append以及extend返回None的例子
Jul 20 Python
Python 仅获取响应头, 不获取实体的实例
Aug 21 Python
Python完全识别验证码自动登录实例详解
Nov 24 Python
Python3 使用map()批量的转换数据类型,如str转float的实现
Nov 29 Python
Pytorch Tensor 输出为txt和mat格式方式
Jan 03 Python
Python os模块常用方法和属性总结
Feb 20 Python
Python如何实现邮件功能
May 27 Python
python中的测试框架
Nov 13 Python
Python实现DBSCAN聚类算法并样例测试
Jun 22 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运算符的知识大全
2011/11/03 PHP
php实现json编码的方法
2015/07/30 PHP
Zend Framework连接Mysql数据库实例分析
2016/03/19 PHP
mac系统下为 php 添加 pcntl 扩展
2016/08/28 PHP
thinkphp实现分页显示功能
2016/12/03 PHP
PHP中使用mpdf 导出PDF文件的实现方法
2018/10/22 PHP
php+Ajax无刷新验证用户名操作实例详解
2019/03/04 PHP
游戏人文件夹程序 ver 4.03
2006/07/14 Javascript
JavaScript ( (__ = !$ + $)[+$] + ({} + $)[_/_] +({} + $)[_/_] )
2011/02/25 Javascript
jQuery on()方法示例及jquery on()方法的优点
2015/08/27 Javascript
jQuery实现彩带延伸效果的网页加载条loading动画
2015/10/29 Javascript
Bootstrap中CSS的使用方法
2016/02/17 Javascript
javascript加减乘除的简单实例
2016/07/12 Javascript
js实现表单及时验证功能 用户信息立即验证
2016/09/13 Javascript
jquery事件绑定解绑机制源码解析
2016/09/19 Javascript
Jquery删除css属性的简单方法
2016/12/04 Javascript
node.js平台下利用cookie实现记住密码登陆(Express+Ejs+Mysql)
2017/04/26 Javascript
微信小程序实现获取准确的腾讯定位地址功能示例
2019/03/27 Javascript
Vue 中文本内容超出规定行数后展开收起的处理的实现方法
2019/04/28 Javascript
详解从vue-loader源码分析CSS Scoped的实现
2019/09/23 Javascript
Vue实现图片与文字混输效果
2019/12/04 Javascript
vue 表单输入框不支持focus及blur事件的解决方案
2020/11/17 Vue.js
python实现apahce网站日志分析示例
2014/04/02 Python
Python中声明只包含一个元素的元组数据方法
2014/08/25 Python
使用sklearn之LabelEncoder将Label标准化的方法
2018/07/11 Python
python读取各种文件数据方法解析
2018/12/29 Python
Python pyautogui模块实现鼠标键盘自动化方法详解
2020/02/17 Python
python实现程序重启和系统重启方式
2020/04/16 Python
OpenCV+Python3.5 简易手势识别的实现
2020/12/21 Python
HTML5引入的新数组TypedArray介绍
2012/12/24 HTML / CSS
英国珠宝钟表和家居礼品精品店:David Shuttle
2018/02/24 全球购物
英国电信商店:BT Shop
2019/12/17 全球购物
英国领先的男装设计师服装独立零售商:Repertoire Fashion
2020/10/19 全球购物
财务经理岗位职责
2013/11/09 职场文书
承兑汇票延期证明
2015/06/23 职场文书
canvas绘制折线路径动画实现
2021/05/12 Javascript