利用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列表append和+的区别浅析
Feb 02 Python
Python3读取zip文件信息的方法
May 22 Python
python使用matplotlib绘图时图例显示问题的解决
Apr 27 Python
Python基于TCP实现会聊天的小机器人功能示例
Apr 09 Python
Django Admin实现三级联动的示例代码(省市区)
Jun 22 Python
python+os根据文件名自动生成文本
Mar 21 Python
详解Python基础random模块随机数的生成
Mar 23 Python
python处理“
Jun 10 Python
如何使用Python 打印各种三角形
Jun 28 Python
python 根据网易云歌曲的ID 直接下载歌曲的实例
Aug 24 Python
关于Pytorch MaxUnpool2d中size操作方式
Jan 03 Python
基于Keras 循环训练模型跑数据时内存泄漏的解决方式
Jun 11 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
星际争霸 Starcraft 秘技补丁
2020/03/14 星际争霸
swfupload 多文件上传实现代码
2008/08/27 PHP
Function eregi is deprecated (解决方法)
2013/06/21 PHP
如何使用PHP实现javascript的escape和unescape函数
2013/06/29 PHP
php实现可用于mysql,mssql,pg数据库操作类
2014/12/13 PHP
PHP中关键字interface和implements详解
2017/06/14 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
测试你的JS的掌握程度的代码
2009/12/09 Javascript
jQuery实现的感应鼠标悬停图片色彩渐显效果
2015/03/03 Javascript
基于JS实现PHP的sprintf函数实例
2015/11/14 Javascript
JS中artdialog弹出框控件之提交表单思路详解
2016/04/18 Javascript
Vue常用指令V-model用法
2017/03/08 Javascript
bootstrap里bootstrap动态加载下拉框的实例讲解
2018/08/10 Javascript
JS实现的类似微信聊天效果示例
2019/01/29 Javascript
Koa 中的错误处理解析
2019/04/09 Javascript
layui 对弹窗 form表单赋值的实现方法
2019/09/04 Javascript
JS实现关闭小广告特效
2021/01/29 Javascript
使用webpack搭建vue环境的教程详解
2019/12/31 Javascript
nodejs中内置模块fs,path常见的用法说明
2020/11/07 NodeJs
[01:01:24]DOTA2上海特级锦标赛A组败者赛 EHOME VS CDEC第三局
2016/02/25 DOTA
Python使用线程来接收串口数据的示例
2019/07/02 Python
Python threading的使用方法解析
2019/08/28 Python
简单了解python中的与或非运算
2019/09/18 Python
使用keras实现BiLSTM+CNN+CRF文字标记NER
2020/06/29 Python
利用HTML5中Geolocation获取地理位置调用Google Map API在Google Map上定位
2013/01/23 HTML / CSS
html5 视频播放解决方案
2016/11/06 HTML / CSS
英国的屈臣氏:Boots博姿
2017/12/23 全球购物
美国最大的存储市场:SpareFoot
2018/07/23 全球购物
中国双语服务优势的在线购票及活动平台:247tickets
2018/10/26 全球购物
兰蔻英国官网:Lancome英国
2019/04/30 全球购物
《月球之谜》教学反思
2014/04/10 职场文书
村党支部书记承诺书
2014/05/29 职场文书
2014国庆节标语口号
2014/09/19 职场文书
面试复试通知单
2015/04/24 职场文书
2016元旦晚会主持人开场白和结束语
2015/12/03 职场文书
服务器间如何实现文件共享
2022/05/20 Servers