利用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编程中JSON模块的使用
Oct 15 Python
简单讲解Python中的数字类型及基本的数学计算
Mar 11 Python
python实现的正则表达式功能入门教程【经典】
Jun 05 Python
Python2和Python3中urllib库中urlencode的使用注意事项
Nov 26 Python
Python3 jupyter notebook 服务器搭建过程
Nov 30 Python
详解django+django-celery+celery的整合实战
Mar 19 Python
python实现文件的备份流程详解
Jun 18 Python
python中dict使用方法详解
Jul 17 Python
python可迭代对象去重实例
May 15 Python
Django自带用户认证系统使用方法解析
Nov 12 Python
python析构函数用法及注意事项
Jun 22 Python
python字符串拼接.join()和拆分.split()详解
Nov 23 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
第4章 数据处理-php数组的处理-郑阿奇
2011/07/04 PHP
Laravel jwt 多表(多用户端)验证隔离的实现
2019/12/18 PHP
json跟xml的对比分析
2008/06/10 Javascript
jquery ready()的几种实现方法小结
2010/06/18 Javascript
基于jQuery的图片大小自动适应实现代码
2010/11/17 Javascript
超简单JS二级、多级联动的简单实例
2014/02/18 Javascript
PHP中使用微秒计算脚本执行时间例子
2014/11/19 Javascript
jquery中checkbox使用方法简单实例演示
2015/11/24 Javascript
JavaScript数据结构与算法之栈与队列
2016/01/29 Javascript
输入法的回车与消息发送快捷键回车的冲突解决方法
2016/08/09 Javascript
详解JS中定时器setInterval和setTImeout的this指向问题
2017/01/06 Javascript
Vue-cli Eslint在vscode里代码自动格式化的方法
2018/02/23 Javascript
浅谈React Native 传参的几种方式(小结)
2019/05/21 Javascript
Vue项目中Api的组织和返回数据处理的操作
2019/11/04 Javascript
微信小程序地图绘制线段并且测量(实例代码)
2020/01/02 Javascript
JS简单表单验证功能完整示例
2020/01/26 Javascript
javascript实现移动端上传图片功能
2020/08/18 Javascript
python实现对象列表根据某个属性排序的方法详解
2019/06/11 Python
Python 的AES加密与解密实现
2019/07/09 Python
在python中实现求输出1-3+5-7+9-......101的和
2020/04/02 Python
python pymysql链接数据库查询结果转为Dataframe实例
2020/06/05 Python
用60行代码实现Python自动抢微信红包
2021/02/04 Python
CSS3实现的闪烁跳跃进度条示例(附源码)
2013/08/19 HTML / CSS
锐步英国官网:Reebok英国
2019/11/29 全球购物
.net软件工程师应聘上机试题
2015/03/10 面试题
JSP和EJB可以共享HttpSession么?EJB里面可以改变session里面的内容
2013/06/05 面试题
经管应届生求职信
2013/11/17 职场文书
高中毕业的自我鉴定
2013/12/09 职场文书
农村婚礼主持词
2014/03/13 职场文书
财务部副经理岗位职责范本
2014/06/17 职场文书
会议欢迎标语
2014/06/30 职场文书
党员四风自我剖析材料
2014/10/07 职场文书
2014年销售经理工作总结
2014/12/01 职场文书
小学数学国培研修日志
2015/11/13 职场文书
《正面管教》读后有感:和善而坚定的旅程
2019/12/19 职场文书
oracle数据库去除重复数据
2022/05/20 Oracle