利用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内置函数OCT详解
Nov 09 Python
python入门基础之用户输入与模块初认识
Nov 14 Python
代码讲解Python对Windows服务进行监控
Feb 11 Python
python贪婪匹配以及多行匹配的实例讲解
Apr 19 Python
Python使用pickle模块实现序列化功能示例
Jul 13 Python
对python同一个文件夹里面不同.py文件的交叉引用方法详解
Dec 15 Python
Python中的random.uniform()函数教程与实例解析
Mar 02 Python
PyQt5 实现字体大小自适应分辨率的方法
Jun 18 Python
下载与当前Chrome对应的chromedriver.exe(用于python+selenium)
Jan 14 Python
Python同时迭代多个序列的方法
Jul 28 Python
Python中的套接字编程是什么?
Jun 21 Python
python编程项目中线上问题排查与解决
Nov 01 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
Zend Studio使用技巧两则
2016/04/01 PHP
Linux平台php命令行程序处理管道数据的方法
2016/11/10 PHP
js兼容标准的表格变色效果
2008/06/28 Javascript
JavaScript与Div对层定位和移动获得坐标的实现代码
2010/09/08 Javascript
基于jquery插件实现常见的幻灯片效果
2013/11/01 Javascript
JavaScript实现的双向跨域插件分享
2015/01/31 Javascript
jquery实现公告翻滚效果
2015/02/27 Javascript
在JavaScript中操作时间之getMonth()方法的使用
2015/06/10 Javascript
Bootstrap弹出带合法性检查的登录框实例代码【推荐】
2016/06/23 Javascript
利用Console来Debug的10个高级技巧汇总
2018/03/26 Javascript
在 Vue.js中优雅地使用全局事件的方法
2019/02/01 Javascript
Vue项目中使用jsonp抓取跨域数据的方法
2019/11/10 Javascript
非常漂亮的js烟花效果
2020/03/10 Javascript
解决angular 使用原生拖拽页面卡顿及表单控件输入延迟问题
2020/04/21 Javascript
JavaScript实现浏览器网页自动滚动并点击的示例代码
2020/12/05 Javascript
[51:26]VP vs VG 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
在Gnumeric下使用Python脚本操作表格的教程
2015/04/14 Python
Python实现12306火车票抢票系统
2019/07/04 Python
Pytorch反向求导更新网络参数的方法
2019/08/17 Python
Python pandas RFM模型应用实例详解
2019/11/20 Python
Python输出指定字符串的方法
2020/02/06 Python
Python爬取数据并实现可视化代码解析
2020/08/12 Python
使paramiko库执行命令时在给定的时间强制退出功能的实现
2021/03/03 Python
推荐10个HTML5响应式框架
2016/02/25 HTML / CSS
Fashion Eyewear美国:英国线上设计师眼镜和太阳镜的零售商
2016/08/15 全球购物
荷兰睡眠专家:Beter Bed
2020/11/23 全球购物
继承公证书样本
2014/04/04 职场文书
庆六一宣传标语
2014/10/08 职场文书
2014年德育工作总结
2014/11/20 职场文书
先进工作者事迹材料
2014/12/23 职场文书
2015年安康杯竞赛活动总结
2015/03/26 职场文书
债务追讨律师函
2015/06/24 职场文书
寒假生活随笔
2015/08/15 职场文书
2016三严三实专题教育活动心得体会
2016/01/06 职场文书
python编写函数注意事项总结
2021/03/29 Python
P站美图推荐——变身女主角特辑
2022/03/20 日漫