使用线程与Python解释器交互式交谈

时间:2014-08-24 13:05:57

标签: java python multithreading

我已经制作了三个课程:ReaderWriterMain课程。 Reader类使用线程来侦听Python解释器标准输出的输出。

Reader类:

package main;
import java.io.BufferedReader;
import java.io.IOException;
public class Reader implements Runnable {
    private volatile BufferedReader br;
    private volatile String output;
    private Thread thread;
    private Boolean stop;
    public Reader(BufferedReader br) {
        this.br = br;
        this.output = "";
        this.thread = new Thread(this);
        this.stop = false;
    }
    public void run() {
        while(!stop) {
            this.read();
        }
    }
    private synchronized void read() {
        try {
            if(br.ready()) {
                this.output = br.readLine();
                            System.out.println(this.output);
                notify();
                wait();
            }
        }
        catch(Exception error) {
            error.printStackTrace();
        }
    }
    public synchronized String getOutput() {
        try {
            wait();
        }
        catch(Exception error) {
            error.printStackTrace();
        }
        notify();
        return this.output;
    }
    public void startListening() {
        this.thread.start();
    }
    public void close() throws IOException {
        this.stop = true;
        this.br.close();
    }
}

这里是Writer班级:

package main;
import java.io.BufferedWriter;
import java.io.IOException;
public class Writer {
    private BufferedWriter bw;
    public Writer(BufferedWriter bw) {
        this.bw = bw;
    }
    public void write(String line) {
        try {
            this.bw.write(line);
            this.bw.newLine();
            this.bw.flush();
        }
        catch(Exception error) {
            error.printStackTrace();
        }
    }
    public void close() throws IOException {
        this.bw.close();
    }
}

最后,Main类看起来如下所示。

package main;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
public class Main {
    public static void main(String[] args) throws IOException, 
                                        InterruptedException {
        ProcessBuilder processBuilder = new ProcessBuilder("/usr/bin/script",
                                                   "-qfc", "/usr/bin/python",
                                                                "/dev/null");
        Process process = processBuilder.start();
        InputStream inputStream = process.getInputStream();
        InputStreamReader isr = new InputStreamReader(inputStream);
        BufferedReader br = new BufferedReader(isr);
        OutputStream outputStream = process.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(outputStream);
        BufferedWriter bw = new BufferedWriter(osw);
        Writer writer = new Writer(bw);
        Reader reader = new Reader(br);
        reader.startListening();
        writer.write("2+2");
        System.out.print(reader.getOutput());
    }
}

我得到的唯一结果是两行输出,第一行在第二行开始时重复。

Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36).
Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36). [GCC 4.4.7] on linux2

尽管剩余更多输出,readLine方法看起来仍然没有继续阅读。为什么?我希望我的程序能够给出结果,如下几行所示。提前感谢您的帮助。

Python 2.6.6 (r266:84292, Jan 22 2014, 09:42:36) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 2+2
4
>>>

2 个答案:

答案 0 :(得分:1)

只有一种正确的方法可以使用wait()notify()

首先,必须有明确的条件。它可以是布尔变量,也可以是布尔函数,但它必须是可以测试的东西。

其次,必须存在与条件关联的锁定,并且任何可以更改条件的代码必须仅在锁定锁定时执行此操作。

最后,wait()的线程必须在循环中执行。

看起来像这样:

// lock is private so we don't have to worry about outside code messing with our synchronization.
private final Object lock = new Object();

// condition is private so we don't have to worry that outside code could break the contract
// (i.e., change the condition without locking the lock.)
private boolean condition = false;

// called by thread A
void waitForIt() {
    synchronized(lock) {
        while (! condition) {
            // At this point condition is false.  We don't have to worry about a "lost
            // notification" because this thread holds the lock, and will continue to 
            // hold it until it is in the wait() call, ready to be notified.
            lock.wait();
            // condition might *not* be true at this point because thread C might have
            // set it false in the interval between when thread B called notify(), and
            // when this thread finally re-acquired the lock and returned from wait().
        }
        // condition *is* guaranteed to be true here because we've tested it, and
        // we have the exclusive lock.
        doSomethingThatRequiresConditionToBeTrue();
    }
    // condition is no longer guaranteed true after leaving the synchronized block.
}

// called by thread B
void makeItHappen() {
    synchronized(lock) {
        if (! condition) {
            doSomethingThatMakesConditionTrue();
            condition = true;
            lock.notify();
        }
    }
}

// called by thread C
void reset() {
    synchronized(lock) {
        doSomethingThatMakesConditionFalse();
        condition = false;
    }
}

通过这种设计,通知不会丢失"如果线程B在线程A调用makeItHappen()之前调用waitForIt(). waitForIt()函数不会盲目地等待:它只在条件显式为假时等待。

答案 1 :(得分:1)

如下面的答案所示,您使用了synchronizednotifywaitvolatile以不符合您认为的方式行事的System.out.printlnReader startListening() entry Writer write() entry. line: 2+2 Writer write() exit. Reader getOutput() entry Reader getOutput() wait... Reader run() entry Reader read() entry Reader read() exit <snip - lots and lots of empty read() entry and exits...> Reader read() br is ready, readLine... Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) Reader read() notify... Reader read() wait... Reader getOutput() notify... Reader getOutput() is returning: Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) Reader read() exit Reader read() entry Reader read() br is ready, readLine... [GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2 Reader read() notify... Reader read() wait... 做。

而不是为您提供一套全新的代码 使用日志语句向您展示您的代码当前正在做什么, 然后提供一组最小的更改,以使您当前的代码工作。

此外,查看您的CPython交互式shell输出,因为它们都是 CPython 2.6.6和Red Hat发行版我猜测你正在使用CentOS。 因此,我对以下内容进行了测试:

  • 运行CentOS 6.5的VirtualBox虚拟机,通过提供 流浪,
  • CPython 2.6.6
  • OpenJDK Java 1.7.0 r65

所以,让我们开始吧。我撒了很多Reader getOutput()个电话 到处;每个函数入口,函数出口和决策一个 (例如,如果陈述)。我不会在这里发布完整的输出 是一个摘要版本:

wait()

使用适当的日志记录库可以更容易地阅读 线程名称或ID,但因为我们可以猜出涉及哪些线程 让我们解开这个:

  • 主线程运行流程
  • 主线程写入&#34; 2 + 2&#34;马上到它,而不是等待它 准备阅读输入。
  • 在Reader线程启动之前,主线程调用 Reader run() entry。此实例方法是同步的,因此 主线程在调用Reader read()时阻塞,释放Reader 实例监视器锁定,并等待某人通知它。
  • 阅读器主题开始,我们可以看到br.readLine()
  • 慢慢地抓住了这个过程。 CPython需要很长时间才能开始! 这解释了所有空notify()次来电。
  • 最终CPython启动。
  • Reader线程调用synchronized,打印出一行 输出,然后调用wait()。有趣!我们有什么锁 我们正在通知?实例锁被抓住了 Reader read() notify关键字,即Reader实例!我们知道的 主线程是当前notify()在Reader上的唯一线程 object作为监视器,因此该调用会导致主线程被唤醒。
  • 此时请注意Reader线程已打印出一个 输出线。这是CPython的第一行输出 &#34; Python 2.6.6&#34;线。
  • notify()点,问问自己:什么是主要的 线程干嘛?我们在Reader实例上wait(),所以肯定是 主线程正在运行?没有! notify()的JavaDoc非常好 清除:&#34;唤醒的线程将无法继续直到 当前线程放弃对该对象的锁定。&#34;。所以在这 指向读者线程告诉主线程#34;醒来,但等待 直到我放弃了Reader实例监视器&#34;。
  • Reader线程调用getOutput(),因此放弃Reader 实例监视器对象并进入睡眠状态。这导致主要 线程恢复执行。
  • 主线程调用notify()。采用与此前相同的逻辑 唤醒Reader线程,但Reader线程可能只会恢复 一旦主线程放弃了Reader实例监视器。
  • 主线程对wait()的调用已完成。这回来了 第一行&#34; Python 2.6.6&#34;再次。这解释了为什么这一行 打印两次。
  • 主线程停止运行。故事结局?没有!记住Reader线程 恢复执行。由于Reader线程不是守护程序线程,因此JVM无法退出。
  • Reader线程打印出下一行执行。
  • Reader线程调用wait()。这个电话无关紧要, 因为主线程不再expect在这个锁上。
  • Reader线程调用package main; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; public class Main { public static boolean isProcessFinished(Process process) { try { process.exitValue(); } catch (IllegalThreadStateException e) { return false; } return true; } public static void printAllOutput(Reader reader) { String line; while ((line = reader.getOutput()) != null) { System.out.println(line); } } public static void main(String[] args) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder( "/usr/bin/script", "-qfc", "/usr/bin/python", "/dev/null"); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); InputStreamReader isr = new InputStreamReader(inputStream); BufferedReader br = new BufferedReader(isr); OutputStream outputStream = process.getOutputStream(); OutputStreamWriter osw = new OutputStreamWriter(outputStream); BufferedWriter bw = new BufferedWriter(osw); try ( Writer writer = new Writer(bw); Reader reader = new Reader(br); ) { reader.startListening(); writer.write("2+2"); writer.write("exit()"); while(!isProcessFinished(process)) { // We don't block here. Maybe we're doing something // complicated at the same time. Maybe we're a web // server. Maybe... printAllOutput(reader); // Without this the java process hits 100% CPU. // This is because we're furiously checking if the // process has exited and looking for output. Java's // process handling API isn't so hot; consider using // another library? Thread.sleep(100); } // Yes, the process has finished, but there may still be output // to be read from the BufferedReader. Let's grab any dog-ends. printAllOutput(reader); } } } 。由于主线程不再存在 周围通知它唤醒程序永远阻止。

需要一段时间才能调试!我们学到了什么?的多线程 编程真的很难。我们只有两个线程!在这 通过奇怪的使用来实现复杂性的情况 同步方法并等待并通知同步方法。

我们知道其他的事情。这个计划没有合理的理由 打印出程序的所有输出行。这是因为 线程尚未在此程序中有效使用。

在我继续之前,我应该强调写一个期待的 Java中的程序并非易事。我强烈建议你重复使用 执行此操作的现有库,直接package main; import java.io.BufferedReader; import java.io.IOException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; public class Reader implements Runnable, AutoCloseable { private BufferedReader br; private Thread thread; private Queue<String> output = new ConcurrentLinkedQueue<>(); private AtomicBoolean stop = new AtomicBoolean(); public Reader(BufferedReader br) { this.br = br; this.thread = new Thread(this); } public void run() { System.out.println("Reader run() entry"); while(!stop.get()) { try { // Why not just avoid the ready() call and block // on readLine()? We want the Main thread to be able // to stop the Reader thread at any time. Hence the // Reader thread needs to periodically check to see // if it has been requested to stop, and moreover // the Reader thread cannot block. while (br.ready()) { output.add(br.readLine()); } } catch (IOException e) { e.printStackTrace(); stop.set(true); } } try { br.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("Reader run() exit"); } public String getOutput() { return output.poll(); } public void startListening() { System.out.println("Reader startListening() entry"); this.thread.start(); } public void close() throws IOException { // Note that the close() method is called by the Main thread // and merely sets a flag. This flag will eventually be read // by the Reader thread which will then close the // BufferedReader. this.stop.set(true); } } 或其他库 有人已经写过的Java克隆。

但是,让我们说我们想以最直接的方式按原样修复程序 可能。我认为这对你来说是一个很好的学习机会 在Java中使用线程练习。作为一个起点问问自己: 你想要什么?我们希望能够发送&#34; 2 + 2&#34;运行Python proccess并且不强制主线程阻塞等待输出 从过程中。这表明使用了一个独特的线程 责任是从流中读取输出。那么主线是 可以随时从读者线程获得一行输出 to,或弄清楚当前没有可用的输出。

我们如何知道流程何时完成?当然读者会 不知道。它只是从一个流中读取,从它的角度来看 流是无穷无尽的,如果空了,读者会阻止阅读它!至 保持简单,让我们说这是主要的责任 知道什么时候过程;什么时候Main类会读取所有的 剩余输出,打印,然后退出。

从我们编写此描述的方式来看,读者已经清楚了 应该尽可能频繁地阅读。读者 应该不知道主线程存在。相反,读者 线程应该把它的结果放到一个安全的地方。然后是主线程 可以经常/偶尔或同样地从这个安全的地方读取 主线程想要的很少。这看似简单直接 解决方案是将此输出存储到并发队列中 某种。 Reader线程将添加到头部和主线程 会从尾巴弹出!使用并发队列委托讨厌 将线程同步到线程安全集合的业务,以及 整齐地将读者线程与知道谁正在从中读取。

Main.java:

package main;
import java.io.BufferedWriter;
import java.io.IOException;
public class Writer implements AutoCloseable {
    private BufferedWriter bw;
    public Writer(BufferedWriter bw) {
        this.bw = bw;
    }
    public void write(String line) {
        System.out.println("Writer write() entry. line: " + line);
        try {
            this.bw.write(line);
            this.bw.newLine();
            this.bw.flush();
        }
        catch(Exception error) {
            error.printStackTrace();
        }
        System.out.println("Writer write() exit.");
    }
    public void close() throws IOException {
        this.bw.close();
    }
}

Reader.java:

Reader startListening() entry
Writer write() entry. line: 2+2
Writer write() exit.
Writer write() entry. line: exit()
Writer write() exit.
Reader run() entry
Python 2.6.6 (r266:84292, Nov 22 2013, 12:16:22)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 4
>>>
Reader run() exit

Writer.java(除了实现AutoCloseable之外没有改变)

expect

输出:

expect

作为读者的练习 - 你将如何真正地与之互动 Python进程?那么你需要一种向它发送输入的方法 等待&#34;&gt;&gt;&gt;&#34;从它表明它已准备好接收 更多输入。如果执行命令,例如,该怎么办? &#34;打印&#39;&gt;&gt;&gt;&#39;,那 包含提示?那么你肯定会判断是否 解释器已准备好输入,按ENTER然后查看是否 输出&#34;&gt;&gt;&gt;&#34;。但是,如果它正在运行命令而你呢? 按ENTER,肯定会扭曲输出?

慢慢但肯定会重新实施{{1}}。有趣的运动,我 在C中完成了{{1}}的限制版本,但这并非易事。