[Python] subprocess.Popen 으로 stdout, stderr non-blocking 으로 결과 가져오기

다음과 같은 파이썬 샘플이 있습니다. (po_callee.py)

import sys
import time
import datetime
import argparse


################################################################################
def do(args):
    for i in range(args.loop):
        if 0 < i and args.stderr > 0 and i % args.stderr == 0:
            msg = '[%s] message stderr [%d]\n' % (datetime.datetime.now(), i)
            sys.stderr.write(msg)
        else:
            msg = '[%s] message stdout [%d]\n' % (datetime.datetime.now(), i)
            sys.stdout.write(msg)
        time.sleep(1)


################################################################################
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Popen callee test program')
    parser.add_argument('--loop', type=int,
                        default=30,
                        help='loop for the test, default is 30')
    parser.add_argument('--stderr', type=int,
                        default=10,
                        help='every other n print stdout, default is 10. 0 means no stderr')
    _args = parser.parse_args()
    do(_args)

이 프로그램은 다음과 같이 30개의 결과를 출력하는데 

[2019-06-25 11:31:14.465098] message stdout [0]
[2019-06-25 11:31:15.476131] message stdout [1]
[2019-06-25 11:31:16.483655] message stdout [2]
[2019-06-25 11:31:17.490693] message stdout [3]
[2019-06-25 11:31:18.498152] message stdout [4]
[2019-06-25 11:31:19.505642] message stdout [5]
[2019-06-25 11:31:20.513192] message stdout [6]
[2019-06-25 11:31:21.520630] message stdout [7]
[2019-06-25 11:31:22.527671] message stdout [8]
[2019-06-25 11:31:23.535174] message stdout [9]
[2019-06-25 11:31:24.542701] message stderr [10]
[2019-06-25 11:31:25.550154] message stdout [11]
[2019-06-25 11:31:26.557651] message stdout [12]
[2019-06-25 11:31:27.565560] message stdout [13]
[2019-06-25 11:31:28.572739] message stdout [14]
[2019-06-25 11:31:29.579713] message stdout [15]
[2019-06-25 11:31:30.587164] message stdout [16]
[2019-06-25 11:31:31.594650] message stdout [17]
[2019-06-25 11:31:32.602148] message stdout [18]
[2019-06-25 11:31:33.609651] message stdout [19]
[2019-06-25 11:31:34.617144] message stderr [20]
[2019-06-25 11:31:35.624593] message stdout [21]
[2019-06-25 11:31:36.631692] message stdout [22]
[2019-06-25 11:31:37.639128] message stdout [23]
[2019-06-25 11:31:38.647124] message stdout [24]
[2019-06-25 11:31:39.654169] message stdout [25]
[2019-06-25 11:31:40.662560] message stdout [26]
[2019-06-25 11:31:41.669633] message stdout [27]
[2019-06-25 11:31:42.679560] message stdout [28]
[2019-06-25 11:31:43.684141] message stdout [29]

그 중에 2개의 stderr 출력이 있습니다.

그런데 다른 파이썬 스크립트에서 위의 python을 호출하는데,

일반적으로는

import subprocess
po = Popen([sys.executable, 'po_callee.py'], stdout=PIPE, stderr=PIPE)
print(po.stdout.read())
print(po.stderr.read())

와 같이 하는데 이 때에는 po.stdout.read() 에서 모든 결과를 다 읽을 때까지
(해당 스트림이 닫히기 전까지) 기다립니다.

해당 read() 대신, readline() 등등도 해 보았고,
결국 select 등을 사용하여 보았지만,
윈도우에서는 제대로 지원안되는 단점이 존재합니다.
pipe를 써서 된다고도 누군가는 그랬는데, 그것도 실패했습니다.

결국 이를 다음과 같이 해결하였습니다.


import sys
import time
from subprocess import PIPE, Popen
from threading import Thread
from queue import Queue, Empty

ON_POSIX = 'posix' in sys.builtin_module_names


################################################################################
def enqueue_stdout(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


################################################################################
def enqueue_stderr(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()


################################################################################
def do():
    po = Popen([sys.executable, 'po_callee.py'],
              stdout=PIPE, stderr=PIPE,
              bufsize=1, close_fds=ON_POSIX)
    q_out, q_err = Queue(), Queue()
    t_out = Thread(target=enqueue_stdout, args=(po.stdout, q_out))
    t_out.daemon = True  # thread dies with the program
    t_out.start()
    t_err = Thread(target=enqueue_stderr, args=(po.stderr, q_err))
    t_err.daemon = True  # thread dies with the program
    t_err.start()

    while po.poll() is None:
        try:
            line = q_out.get_nowait()
            if line:
                print(line.decode('utf-8').rstrip())
        except Empty:
            pass
        try:
            line = q_err.get_nowait()  # or q.get(timeout=.1)
            if line:
                sys.stderr.write('%s\n' % line.decode('utf-8').rstrip())
        except Empty:
            pass
        time.sleep(1)


################################################################################
if __name__ == '__main__':
    do()

위의 것은 파이썬 쓰레드를 두 개 만들어,
하나는 stdout, 다른 하나는 stderr 용으로 만든 다음, 이것을 해당 Queue에 넣습니다.
그리고 Q에서 get_nowait() 로 non-blocking 으로 결과 값을 가져와서 처리하는 것입니다.

GIL 때문에 파이썬 쓰레드를 사용하지 말라고 하지만, 이것은 어디까지나 CPU bound job인 경우에 해당하므로,
위와 같이 stdout, stderr 로 출력하는 것은 해당되지 않습니다.

여러번 해당 내용을 제대로 해 봐야지 했었는데, 완벽한 방법은 아니더라도,
현재 이렇게 해결하였습니다.


어느 분께는 도움이 되셨기를 ..




[Docker] Swarm stack for compose

현재 컨테이너의 de facto 표준은 Kubernetes 라고 누구나 이야기를 합니다.그런데 몇년 전만 하더라도 Swarm, Mesos 등과 같이 어떤 것이 표준이 될지잘 모르는 상화이었죠.그런데 말입니다, docker로 개발을 할때 다음과 같은 수순을 밟습니다.1)  Presentation, BackEnd logic, Data Access ... » 내용보기

[Python] BeautifulSoup 과 HTML 인코딩에 관한 고찰

얼마전 필요에 따라 BeautifulSoup을 이용하여 HTML 파싱하여 데이터를 추출하는 프로그램을 작성했는데,HTML을 읽고 그 내용을 파싱하기 위하여 다음과 같이 시도했습니다.    with open('1.html', 'r', encoding='utf-8') as ifp:        ... » 내용보기

[해피해킹키보드] 프로2 Type-S

지난번 10년간 사용하고 있던 해피해킹 키보드 프로2를 지훈이에게 사용해 보라고 주고서는,Type-S 버전을 하나 더 장만했습니다.처음에는 블루투스 버전의 해피해킹을 구매해 볼까 했으나,어디서 확인을 해 보니 Type-S 가 더 낫다는 이야기를 듣고는,생각을 바꾸게 되었습니다.도착 예정이 6월8일이었는데, 이건 뭐 주문한 지 이틀만에 일본에서 바로날라 ... » 내용보기

[키보드] 해피해킹 키보드 스페이스 바 돌려 끼워보기

해피해킹 프로2를 구입한 것이 거의 10년이 다 되었습니다.하루에 12시간 또는 그 이상 키보드를 만지작 거리며 생활한 지가 (주중) 거의 35년은족히 지났으니 그동안 키인한 키보드를 생각하면 모르긴 해도 키캡에 각인이다 닳지 않았을까 싶습니다.그동안 여러 키보드를 거쳐 최근 십년간 메인 키보드로 사용하고 있는 것은 해피해킹 키보드 입니다.아래... » 내용보기

구글애드텍스트