繁体   English   中英

如何从`stdin`创建非阻塞连续读取?

[英]How to create non-blocking continuous reading from `stdin`?

我有一个进程,它是这样创建的:

p = subprocess.Popen(args   = './myapp',
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

后来,我正在尝试写pstdin

p.stdin.write('my message\n')

myapp进程具有以下设置:

q = queue.Queue()
def get_input():
    for line in iter(sys.stdin.readline, ''):
        q.put(line)
    sys.stdin.close()

threading.Thread(name   = 'input-getter',
                 target = get_input).start()

它正在尝试不断读取新行,如下所示:

try:
    print('input:', q.get_nowait())
except Empty:
    print('no input')

不幸的是,子进程从不接收任何消息。 当然,当我使用时:

p.communicate('my message\n')

子进程收到消息,但正如预期的那样, communicate方法关闭了pstdin ,因此不再进行通信。

p = subprocess.Popen(args   = './myapp',
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

while p.poll() is None:
    data = p.stdout.readline()

这将创建一个非阻塞的进程读取,直到进程退出。 但是,这里有一些注意事项需要注意。 例如,如果您也管道stderr ,但不读取它..那么您很可能填充一两个缓冲区,无论如何您将挂起程序。 因此,在手动执行操作时,请务必确保清除任何缓冲区I / O.

一个更好的选择是使用select.epoll()如果可能的话,这只能在unix系统上使用,但会给你带来更好的性能和错误处理:)

epoll = select.epoll()
epoll.register(p.stdout.fileno(), select.EPOLLHUP) # Use select.EPOLLIN for stdin.

for fileno, event in epoll.poll(1):
    if fileno == p.stdout.fileno():
        # ... Do something ...

注意:请记住,只要进程需要输入,它通常通过stdout指示这一点,因此您仍然会使用select.epoll注册STDOUT以检查“等待输入”。 您可以注册select.EPOLLIN来检查是否给出了输入,但我几乎看不到这一点,因为请记住,您选择输入到您应该已经知道的过程中的是“正在发生”。

检查进程是否需要输入

您可以使用select.epoll检查进程是否正在等待输入,而不会阻止应用程序执行上述示例。 但还有更好的选择。

Pexpect是一个能够很好地完成它的库并且可以使用SSH

它与子进程有点不同,但可能是一个不错的选择。

让subprocess.popen使用SSH

我将重定向到另一个问题+答案,如果这是你所追求的(因为SSH将以受保护的方式产生一个stdin

Python + SSH密码验证(没有外部库或公钥/私钥)?

我想你可能只是没有看到正在发生的事情的输出。 这是一个似乎在我的盒子上工作的完整示例,除非我完全误解了你想要的东西。 我做的主要更改是将stdout for p设置为sys.stdout而不是subprocess.PIPE 也许我误解了你问题的主旨,这一点至关重要......

这是完整的代码和输出:

在发送(测试)过程中(我将其命名为test_comms.py)。 我目前在Windows上,所以请原谅.bat

import time
import subprocess
import sys

# Note I'm sending stdout to sys.stdout for observation purposes
p = subprocess.Popen(args = 'myapp.bat',
                     stdin  = subprocess.PIPE,
                     stdout = sys.stdout,
                     universal_newlines=True)

#Send 10 messages to the process's stdin, 1 second apart                    
for i in range(10):
    time.sleep(1)
    p.stdin.write('my message\n')

myapp.bat很简单:

echo "In the bat cave (script)"
python myapp.py

myapp.py包含(使用Queue而不是queue - 当前环境Python 2):

import Queue
from Queue import Empty
import threading
import sys
import time

def get_input():
    print("Started the listening thread")
    for line in iter(sys.stdin.readline, ''):
        print("line arrived to put on the queue\n")
        q.put(line)
    sys.stdin.close()

print("Hi, I'm here via popen")    
q = Queue.Queue()

threading.Thread(name   = 'input-getter',
                 target = get_input).start()

print("stdin listener Thread created and started")

# Read off the queue - note it's being filled asynchronously based on 
# When it receives messages.  I set the read interval below to 2 seconds
# to illustrate the queue filling and emptying.
while True:
    time.sleep(2)
    try:
        print('Queue size is',q.qsize())
        print('input:', q.get_nowait())
    except Empty:
        print('no input')

print("Past my end of code...")

输出:

D:\>comms_test.py

D:\>echo "In the bat cave (script)"
"In the bat cave (script)"

D:\>python myapp.py
Hi, I'm here via popen
Started the listening threadstdin listener Thread created and started

line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 2)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 3)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 4)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue

('Queue size is', 5)
('input:', 'my message\n')
line arrived to put on the queue

line arrived to put on the queue


D:\>('Queue size is', 6)
('input:', 'my message\n')
('Queue size is', 5)
('input:', 'my message\n')
('Queue size is', 4)
('input:', 'my message\n')
('Queue size is', 3)
('input:', 'my message\n')
('Queue size is', 2)
('input:', 'my message\n')
('Queue size is', 1)
('input:', 'my message\n')
('Queue size is', 0)
no input
('Queue size is', 0)
no input
('Queue size is', 0)
no input

对于一切正常工作,您必须在主进程( p.stdout )和子进程( sys.stdout )中刷新输出。

communicate是否同时冲洗:

  • 它在关闭时刷新p.stdin
  • 它等待刷新sys.stdout输出(在退出之前)

工作main.py例子

import subprocess,time
import sys
p = subprocess.Popen(args   = ['python3', './myapp.py'],
                     stdin  = subprocess.PIPE,
                     stdout = subprocess.PIPE,
                     universal_newlines=True)

time.sleep(0.5)
p.stdin.write('my message\n')
p.stdin.flush()
#print("ici")
for i,l in  enumerate(iter(p.stdout.readline, ''),start=1):

    print("main:received:",i,repr(l))
    if i == 6:
        break
    print("mainprocess:send:other message n°{}".format(i))
    p.stdin.write("other message n°{}\n".format(i))
    p.stdin.flush()

print("main:waiting for subprocess")
p.stdin.close()    
p.wait()

myapp.py导入队列,线程,sys,time,rpdb的示例

q = queue.Queue()
def get_input():
    for line in iter(sys.stdin.readline, ''):
        q.put(line)
    sys.stdin.close()

threading.Thread(name   = 'input-getter',
                 target = get_input).start()
for i in range(6):
    try:
        l= q.get_nowait()
        print('myapp:input:', l,end="")
        sys.stdout.flush()

    except queue.Empty:
        print("myapp:no input")
        sys.stdout.flush()    
        time.sleep(1)

结果:

main:received: 1 'myapp:no input\n'
mainprocess:send:other message n°1
main:received: 2 'myapp:input: my message\n'
mainprocess:send:other message n°2
main:received: 3 'myapp:input: other message n°1\n'
mainprocess:send:other message n°3
main:received: 4 'myapp:no input\n'
mainprocess:send:other message n°4
main:received: 5 'myapp:input: other message n°2\n'
mainprocess:send:other message n°5
main:received: 6 'myapp:input: other message n°3\n'
main:waiting for subprocess

试图调查你的程序,我写了自己的“不断流动的东西给cat并抓住它返回的程序”。 我没有实现它的子进程端,但希望结构类似。

这条线对你的节目很奇怪......

for line in iter(sys.stdin.readline, ''):
    q.put(line)
sys.stdin.close()

这看起来非常像

for line in stdin:
    q.put(line)

请注意,当管道关闭时,循环将结束,之后无需重新关闭。

如果你需要不断地异步读取stdin,你应该能够在下面的代码中构造一个与child_reader几乎相同的读取线程。 只需用stdin替换child.stdout

import subprocess
import threading
import random

# We may need to guard this?
child = subprocess.Popen('cat', stdout=subprocess.PIPE, stdin=subprocess.PIPE)

# Continuously print what the process outputs...
def print_child():
    for line in child.stdout:
        print(line)

child_reader = threading.Thread(target = print_child)
child_reader.start()

for i in range(10000):
    chars = 'ABC\n'
    child.stdin.write(random.choice(chars).encode())

# Send EOF.
# This kills the cat.
child.stdin.close()

# I don't think order matters here?
child.wait()
child_reader.join()

我编写了一个程序,它基本上都涉及IO异步。 它读取线程上的输入,它在线程上输出,它创建一个进程,并在线程上与该进程通信。

我不确定你的程序需要完成什么,但希望这段代码能够完成它。

# Asynchronous cat program!

# Asynchronously read stdin
# Pump the results into a threadsafe queue
# Asynchronously feed the contents to cat
# Then catch the output from cat and print it
# Thread all the things

import subprocess
import threading
import queue
import sys

my_queue = queue.Queue()

# Input!
def input_method():
    for line in sys.stdin: # End on EOF
        if line == 'STOP\n': # Also end on STOP
            break
        my_queue.put(line)
input_thread = threading.Thread(target=input_method)
input_thread.start()

print ('Input thread started')


# Subprocess!
cat_process = subprocess.Popen('cat', stdout=subprocess.PIPE, stdin=subprocess.PIPE)

print ('cat process started')

queue_alive = True
# Continuously dump the queue into cat
def queue_dump_method():
    while queue_alive:
        try:
            line = my_queue.get(timeout=2)
            cat_process.stdin.write(line.encode())
            cat_process.stdin.flush() # For some reason, we have to manually flush
            my_queue.task_done() # Needed?
        except queue.Empty:
            pass
queue_dump_thread = threading.Thread(target = queue_dump_method)
queue_dump_thread.start()

print ('Queue dump thread started')

# Output!
def output_method():
    for line in cat_process.stdout:
        print(line)
output_thread = threading.Thread(target=output_method)
output_thread.start()

print ('Output thread started')


# input_thread will die when we type STOP
input_thread.join()
print ('Input thread joined')

# Now we wait for the queue to finish processing
my_queue.join()
print ('Queue empty')

queue_alive = False
queue_dump_thread.join()
print ("Queue dump thread joined")

# Send EOF to cat
cat_process.stdin.close()

# This kills the cat
cat_process.wait()
print ('cat process done')

# And make sure we're done outputting
output_thread.join()
print ('Output thread joined')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM