利用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的web框架中的Model的教程
Apr 29 Python
Python标准库06之子进程 (subprocess包) 详解
Dec 07 Python
python urllib爬取百度云连接的实例代码
Jun 19 Python
python使用Apriori算法进行关联性解析
Dec 21 Python
Request的中断和ErrorHandler实例解析
Feb 12 Python
pandas 对日期类型数据的处理方法详解
Aug 08 Python
python TK库简单应用(实时显示子进程输出)
Oct 29 Python
基于python实现雪花算法过程详解
Nov 16 Python
在Pytorch中计算卷积方法的区别详解(conv2d的区别)
Jan 03 Python
解决python-docx打包之后找不到default.docx的问题
Feb 13 Python
Python unittest装饰器实现原理及代码
Sep 08 Python
Python特殊属性property原理及使用方法解析
Oct 09 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
博士208HAF收音机实习报告
2021/03/02 无线电
利用php获取服务器时间的实现代码
2013/06/07 PHP
discuz加密解密函数使用方法和中文注释
2014/01/21 PHP
php实现paypal 授权登录
2015/05/28 PHP
php发送html格式文本邮件的方法
2015/06/10 PHP
php使用CURL模拟GET与POST向微信接口提交及获取数据的方法
2016/09/23 PHP
ThinkPHP5.0框架使用build 自动生成模块操作示例
2019/04/11 PHP
laravel框架模板之公共模板、继承、包含实现方法分析
2019/08/30 PHP
javascript游戏开发之《三国志曹操传》零部件开发(二)人物行走的实现
2013/01/23 Javascript
JavaScript实现为指定对象添加多个事件处理程序的方法
2015/04/17 Javascript
jQuery UI设置固定日期选择特效代码分享
2015/08/27 Javascript
js识别uc浏览器的代码
2015/11/06 Javascript
jQuery实现分隔条左右拖动功能
2015/11/21 Javascript
JavaScript使用DeviceOne开发实战(一) 配置和起步
2015/12/01 Javascript
分享有关jQuery中animate、slide、fade等动画的连续触发、滞后反复执行的bug
2016/01/10 Javascript
javascript原型继承工作原理和实例详解
2016/04/07 Javascript
jquery获取img的src值的简单实例
2016/05/17 Javascript
laypage分页控件使用实例详解
2016/05/19 Javascript
使用JS 插件qrcode.js生成二维码功能
2017/02/20 Javascript
ES6新数据结构Map功能与用法示例
2017/03/31 Javascript
JavaScript基本语法_动力节点Java学院整理
2017/06/26 Javascript
解决vue2.x中数据渲染以及vuex缓存的问题
2017/07/13 Javascript
微信小程序实现YDUI的ScrollTab组件
2018/02/02 Javascript
vue3 源码解读之 time slicing的使用方法
2019/10/31 Javascript
js实现全选和全不选
2020/07/28 Javascript
如何利用nodejs自动定时发送邮件提醒(超实用)
2020/12/01 NodeJs
Python常用时间操作总结【取得当前时间、时间函数、应用等】
2017/05/11 Python
Python读取sqlite数据库文件的方法分析
2017/08/07 Python
英国知名化妆品网站:Revolution Beauty(原TAM Beauty)
2018/02/28 全球购物
在什么时候需要使用"常引用"
2015/12/31 面试题
建筑总经理岗位职责
2014/02/02 职场文书
小学生元旦广播稿
2014/02/21 职场文书
工作求职自荐信
2014/06/13 职场文书
乡镇群众路线教育实践活动整改措施
2014/10/04 职场文书
教师廉政准则心得体会
2016/01/20 职场文书
导游词之西江千户苗寨
2019/12/24 职场文书