利用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中使用PIL库实现图片高斯模糊实例
Feb 08 Python
举例介绍Python中的25个隐藏特性
Mar 30 Python
python中global用法实例分析
Apr 30 Python
Python多进程并发(multiprocessing)用法实例详解
Jun 02 Python
举例讲解如何在Python编程中进行迭代和遍历
Jan 19 Python
Python使用matplotlib的pie函数绘制饼状图功能示例
Jan 08 Python
对pandas中Series的map函数详解
Jul 25 Python
Python3爬虫学习之将爬取的信息保存到本地的方法详解
Dec 12 Python
django组合搜索实现过程详解(附代码)
Aug 06 Python
Python找出列表中出现次数最多的元素三种方式
Feb 24 Python
解决pip安装tensorflow中出现的no module named tensorflow.python 问题方法
Feb 20 Python
pytorch实现手写数字图片识别
May 20 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实现读取和编写XML DOM代码
2010/04/07 PHP
php在程序中将网页生成word文档并提供下载的代码
2012/10/09 PHP
ThinkPHP模板中判断volist循环的最后一条记录的验证方法
2014/07/01 PHP
详解PHP实现执行定时任务
2015/12/21 PHP
javascript 同时在IE和FireFox获取KeyCode的代码
2010/02/07 Javascript
JS 有趣的eval优化输入验证实例代码
2013/09/22 Javascript
MyEclipse取消验证Js的两种方法
2013/11/14 Javascript
jsPDF导出pdf示例
2014/05/02 Javascript
jquery实现对联广告的方法
2015/02/05 Javascript
javascript删除元素节点removeChild()用法实例
2015/05/26 Javascript
JavaScript鼠标特效大全
2016/09/13 Javascript
js实现京东轮播图效果
2017/06/30 Javascript
详解JavaScript中的数组合并方法和对象合并方法
2018/05/11 Javascript
vue解决弹出蒙层滑动穿透问题的方法
2018/09/22 Javascript
JavaScript实现身份证验证代码实例
2019/08/26 Javascript
layui实现数据表格隐藏列的示例
2019/10/25 Javascript
JavaScript装箱及拆箱boxing及unBoxing用法解析
2020/06/15 Javascript
[01:11:27]2018DOTA2亚洲邀请赛小组赛 A组加赛 Newbee vs Optic
2018/04/03 DOTA
[39:18]完美世界DOTA2联赛PWL S3 Forest vs LBZS 第二场 12.17
2020/12/19 DOTA
Python脚本实现集群检测和管理功能
2015/03/06 Python
python常用函数详解
2016/09/13 Python
Python多线程应用于自动化测试操作示例
2018/12/06 Python
Python基础教程之异常详解
2019/01/10 Python
Python中print和return的作用及区别解析
2019/05/05 Python
css3实现画半圆弧线的示例代码
2017/11/06 HTML / CSS
Ariat官网:美国马靴和服装品牌
2019/12/16 全球购物
信用社实习人员自我鉴定
2013/09/20 职场文书
给海归自荐信的建议
2013/12/13 职场文书
学生干部的自我评价分享
2014/01/18 职场文书
志愿者活动总结
2014/04/28 职场文书
霸气押韵的班级口号
2014/06/09 职场文书
法英专业大学生职业生涯规划书范文
2014/09/22 职场文书
2015年十月一日放假通知
2015/08/18 职场文书
高一作文之乐趣
2019/11/21 职场文书
详解Vue slot插槽
2021/11/20 Vue.js
从结婚开始的恋爱故事。小说《我的美好婚事》TV动画化决定
2022/04/07 日漫