Khi bạn hiểu toàn bộ quy trình chạy máy móc trong * unix, bạn sẽ dễ dàng tìm thấy giải pháp đơn giản hơn:
Xem xét ví dụ đơn giản này về cách thực hiện giao tiếp () meth thời gian chờ bằng cách sử dụng select.select () (có sẵn mọi thứ khác trên * nix hiện nay). Điều này cũng có thể được viết bằng epoll / poll / kqueue, nhưng biến thể select.select () có thể là một ví dụ tốt cho bạn. Và những hạn chế chính của select.select () (tốc độ và 1024 max fds) không thể áp dụng cho nhiệm vụ của bạn.
Điều này hoạt động dưới * nix, không tạo luồng, không sử dụng tín hiệu, có thể được xóa khỏi bất kỳ luồng nào (không chỉ chính) và đủ nhanh để đọc 250mb / s dữ liệu từ thiết bị xuất chuẩn trên máy của tôi (i5 2.3ghz).
Có một vấn đề trong việc tham gia stdout / stderr khi kết thúc giao tiếp. Nếu bạn có đầu ra chương trình lớn, điều này có thể dẫn đến việc sử dụng bộ nhớ lớn. Nhưng bạn có thể gọi truyền thông () nhiều lần với thời gian chờ nhỏ hơn.
class Popen(subprocess.Popen):
def communicate(self, input=None, timeout=None):
if timeout is None:
return subprocess.Popen.communicate(self, input)
if self.stdin:
# Flush stdio buffer, this might block if user
# has been writing to .stdin in an uncontrolled
# fashion.
self.stdin.flush()
if not input:
self.stdin.close()
read_set, write_set = [], []
stdout = stderr = None
if self.stdin and input:
write_set.append(self.stdin)
if self.stdout:
read_set.append(self.stdout)
stdout = []
if self.stderr:
read_set.append(self.stderr)
stderr = []
input_offset = 0
deadline = time.time() + timeout
while read_set or write_set:
try:
rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
except select.error as ex:
if ex.args[0] == errno.EINTR:
continue
raise
if not (rlist or wlist):
# Just break if timeout
# Since we do not close stdout/stderr/stdin, we can call
# communicate() several times reading data by smaller pieces.
break
if self.stdin in wlist:
chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
try:
bytes_written = os.write(self.stdin.fileno(), chunk)
except OSError as ex:
if ex.errno == errno.EPIPE:
self.stdin.close()
write_set.remove(self.stdin)
else:
raise
else:
input_offset += bytes_written
if input_offset >= len(input):
self.stdin.close()
write_set.remove(self.stdin)
# Read stdout / stderr by 1024 bytes
for fn, tgt in (
(self.stdout, stdout),
(self.stderr, stderr),
):
if fn in rlist:
data = os.read(fn.fileno(), 1024)
if data == '':
fn.close()
read_set.remove(fn)
tgt.append(data)
if stdout is not None:
stdout = ''.join(stdout)
if stderr is not None:
stderr = ''.join(stderr)
return (stdout, stderr)