Process阻塞问题

本文探讨了一段Java代码中执行批处理文件时遇到的线程阻塞问题,通过分析Runtime.exec与ProcessBuilder的使用,揭示了标准输入流和错误流处理的重要性。作者分享了正确处理流的方法并介绍了ProcessBuilder的合并流优势,以避免JVM缓冲区满导致的阻塞问题。

直接上出问题的代码:

public CommandResult executeFile(File file) {
        ArrayList<String> stdoutList = new ArrayList<String>();
        ArrayList<String> stderrList = new ArrayList<String>();

        try {
            Process p = Runtime.getRuntime().exec("cmd /c " + file.getName(), null, file.getParentFile());
            p.waitFor(); 
            p.getOutputStream().flush();
            p.getOutputStream().close();
            newThreadInputStream(p.getInputStream(), stdoutList).start();
            //newThreadInputStream(p.getErrorStream(), stderrList).start();
        } catch (IOException | InterruptedException e) {
            LOG.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
        return new CommandResult(stdoutList, stderrList);
    }

该方法,是通过Runtime.getRuntime().exec的方式去执行一个批处理文件,代码中可以看到,标准输入流是开启了一个线程去处理了,但是错误流部分的处理给注释掉了。然后,坑人的部分就开始了

在我本地去调用该方法执行批处理文件时,没有任何问题,然后在别人的环境执行时出现了线程阻塞的问题,通过加日志,发现线程在p.waitFor()这里阻塞住了,但我本地又没有事情。

通过查阅资料,得知,waitFor()的位置应该放在对输入流及错误流处理的后边执行,并且对输入流和错误流都要开启线程去处理,否则导致JVM缓冲区满了之后,就会出现阻塞。

修改代码后:

@Override
public CommandResult executeFile(File file) {
    ArrayList<String> stdoutList = new ArrayList<String>();
    ArrayList<String> stderrList = new ArrayList<String>();
    ArrayList<String> list = new ArrayList<String>();
    Process p = null;
    try {
        p = Runtime.getRuntime().exec("cmd /c " + file.getName(), null, file.getParentFile());
        p.getOutputStream().flush();
        p.getOutputStream().close();
        newThreadInputStream(p.getInputStream(), stdoutList).start();
        newThreadInputStream(p.getErrorStream(), stderrList).start();
        LOG.info("waitFor---before");
        p.waitFor();
        LOG.info("waitFor---after");
    } catch (IOException | InterruptedException e) {
        LOG.error("",e);
        throw new RuntimeException(e);
    } finally {
        try {
            p.destroy();
        } catch (Exception e){
            LOG.error("",e);
        }
    }
    return new CommandResult(stdoutList, list);
}

修改后,再次执行,正常。

其实这里我们在通过process执行批处理文件时,可以使用ProcessBuilder而不是Runtime.getRuntime().exec(),ProcessBuilder的好处就是可以合并错误流和输入流可以合并

ProcessBuilder processBuilder = new ProcessBuilder(file + File.separator+"backup.bat");
//将error流合并到标准中
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();

同样的,我们也需要去单开一个线程去处理这个流

process.getInputStream()
<think> 首先,用户的问题是:“我想了解process start操作是阻塞还是非阻塞 请问process start操作是阻塞还是非阻塞”。用户指的是Python中的Process start操作,可能来自multiprocessing模块。 从引用中,我可以看到相关的内容: - 引用[3]提到了Process的使用:`p_obj1 = Process(target=task, args=('jason_sb', ))`和`p_obj1.start()`,其中start方法是用来启动子进程的。 - 引用[3]还说:`p_obj1.start() # 告诉操作系统,去创建一个子进程`,然后提到`join()`方法等待子进程结束。 关键是要确定start操作本身是阻塞还是非阻塞。回想一下阻塞和非阻塞的定义: - 引用[2]定义阻塞操作是指在等待过程中阻塞当前线程,直到操作完成;非阻塞操作立即返回,不阻塞当前线程。 - 引用[1]讨论了Python中的线程阻塞问题,但用户的问题是关于进程的start操作。 在Python的multiprocessing模块中: - `Process.start()`方法是非阻塞的。它启动子进程并立即返回,允许主进程继续执行其他任务。 - 之后,你可以使用`join()`方法等待子进程完成,这会阻塞当前线程直到子进程结束。 从引用[3]的代码示例中: - 它多次调用`start()`并紧跟`join()`,但`start()`本身是异步的。 - 代码中:`p_obj1.start()`后立即`p_obj1.join()`,但这并不意味着start阻塞的;start启动进程,join等待结束。 在引用[5]中,提到multiprocessing.Pool,但同样适用于Processstart是非阻塞的。 我的回答需要: - 使用中文。 - 结构清晰,逐步解释。 - 确保内容真实可靠。 - 遵循系统指令:行内数学用$...$,独立公式用$$...$$(但这里可能不需要数学)。 - 生成相关问题。 - 在引用末尾添加引用标识。 回答结构: 1. 直接回答用户的提问:Process start操作是非阻塞的。 2. 解释为什么是非阻塞的:它立即返回,操作系统异步创建子进程。 3. 对比阻塞操作:例如,join是阻塞的。 4. 提供代码示例来演示。 5. 引用相关段落来支持。 6. 生成相关问题。 引用支持: - 引用[3]:展示了start的使用,暗示它是非阻塞的,因为启动后主进程可以继续。 - 在其他引用中,没有直接说start是非阻塞的,但可以从上下文中推断。 最终回答: - 声明:在Python的multiprocessing模块中,`Process.start()`方法是非阻塞的。 - 解释:调用start()后,它会异步启动新进程,并立即返回控制权给调用者(主进程)。 - 示例:显示一个简单代码,其中start后立即做其他事。 - 注意事项:尽管start是非阻塞的,但你可能需要使用join()来等待子进程完成,以避免主进程过早退出。 - 生成相关问题。 相关问题: 1. 如何等待子进程完成? 2. Python中多进程与多线程的区别? 3. 阻塞和非阻塞操作在并发编程中的应用? </think> ### Process start操作是阻塞还是非阻塞? 在Python的`multiprocessing`模块中,`Process.start()`方法是**非阻塞操作**。下面我将逐步解释原因、行为机制和相关注意事项,确保内容清晰可靠。 #### 1. **`Process.start()`的行为机制** - `Process.start()`用于启动一个新进程(子进程)。 - **非阻塞特性**:调用`start()`时,它会立即返回控制权给主进程(调用线程),而不会等待子进程实际启动或运行完成。操作系统在后台异步创建子进程,主进程可以继续执行后续代码[^3][^5]。 - 例如: ```python from multiprocessing import Process import time def task(name): print(f"子进程 {name} 启动") time.sleep(2) print(f"子进程 {name} 结束") if __name__ == '__main__': p = Process(target=task, args=('test',)) p.start() # 非阻塞:立即返回,主进程继续运行 print("主进程继续执行...") p.join() # 阻塞:等待子进程结束 ``` 输出可能为: ``` 主进程继续执行... 子进程 test 启动 子进程 test 结束 ``` 这里,`start()`后主进程立即打印消息,证明其非阻塞。 #### 2. **与阻塞操作的对比** - **阻塞操作**:如`Process.join()`或文件读取`read_file()`,会挂起当前线程直到操作完成,期间无法执行其他任务[^2][^4]。 - 例如:`p.join()`会阻塞主进程,直到子进程终止。 - **非阻塞操作的优点**:允许主进程在子进程运行期间并发执行其他任务,提高资源利用率,尤其适用于IO密集型或CPU密集型计算[^1][^5]。 #### 3. **关键注意事项** - **子进程启动延迟**:虽然`start()`非阻塞,但操作系统可能需要时间初始化子进程。实际启动可能稍有延迟(毫秒级)。 - **资源管理**:过度启动进程可能导致资源竞争。建议使用`Pool`管理进程数上限[^5]。 - **同步需求**:若主进程需要子进程的结果,必须在`start()`后调用`join()`或同步机制(如队列),否则主进程可能提前退出导致子进程中断[^3]。 #### 4. **为什么是非阻塞?底层原理** - `start()`通过系统调用(如Unix的`fork()`)请求操作系统创建新进程。操作系统将此请求加入调度队列,并立即返回状态给Python解释器,不等待进程实际启动。 - 这符合通用编程模型:进程创建是异步操作,避免浪费CPU周期在等待上[^3][^5]。 #### 5. **引用总结** - `start()`的非阻塞特性使其适合构建高并发应用,如Web服务器处理多个请求[^4]。 - 在Python中,多进程(`multiprocessing`)可绕过GIL限制,实现真正的并行计算,而`start()`是实现并发的关键一步[^1][^5]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值