Ubuntu에서 multiprocessing.Process 상속받은 클래스는 동작하는데 윈도우가면 안됨
사건의 개요
RTSP 영상 수신 프로세스를 만들고 있었다. 이 프로세스는 ffmpeg 라이브러리의 wrapper인 PyAv라는 모듈로 영상을 수신하여 numpy이미지로 읽어주는 간단한 코드이다.
# 별도의 프로세스로 동작하면서 영상을 받아오는 모듈. 오버라이딩 함수인 run함수는 편의상 쓰지 않았다
class IPCamStreamer(multiprocessing.Process):
r
IPCamStreamer : Process which read video stream via rtsp protocol and Write frame on shared memory
def __init__(self, url: str, streamerName: str):
super(IPCamStreamer, self).__init__()
self.url = url
self.name = streamerName
self.daemon = True
self.videoReader = VideoReader(self.url)
def run(self) -> None:
super(IPCamStreamer, self).run()
# PyAv를 이용하여 비디오를 읽어주는 클래스. 일부만 발췌하였다.
class VideoReader:
r
RTSP, Video FILE 등 동영상을 인코딩하여 numpy.ndarray 또는 PIL 이미지로 사용하게 해주는 클래스 입니다.
def __init__(self, path: str = ):
r
:param str path: Path for open video. Ex. /dev/video0, rtsp://<ip>/:8554, ./video/some_video.mp4
self.url = path
self.containerOptions = {
buffer_size: 256000,
stimeout: 20000000,
max_delay: 3000000,
discard_corrupt: DISCARD_CORRUPT,
gen_pts: GENPTS,
non_block: NONBLOCK,
stimeout: 20000000,
max_delay: 3000000
}
if rtsp in path:
self.containerOptions[rtsp_transport] = udp
self.container = None
self._frameNumber = -1
self.container = av.open(path, container_options=self.containerOptions, timeout=(1.0, 0.1), format=None)
위 코드는 Ubuntu에서는 정상 동작하고 윈도우에서는 아래의 에러를 내며 동작하지 않는다.
C:\Users\dspar\PycharmProjects\kisa_rtsp_reciver\venv\Scripts\python.exe C:/Users/dspar/Desktop/Project/human-activity-understanding/ipcam_stream_client.py target_ip.txt
Traceback (most recent call last):
File C:/Users/dspar/Desktop/Project/human-activity-understanding/ipcam_stream_client.py, line 109, in <module>
process.start()
File C:\Users\dspar\AppData\Local\Programs\Python\Python38\lib\multiprocessing\process.py, line 121, in start
self._popen = self._Popen(self)
File C:\Users\dspar\AppData\Local\Programs\Python\Python38\lib\multiprocessing\context.py, line 224, in _Popen
return _default_context.get_context().Process._Popen(process_obj)
File C:\Users\dspar\AppData\Local\Programs\Python\Python38\lib\multiprocessing\context.py, line 327, in _Popen
return Popen(process_obj)
File C:\Users\dspar\AppData\Local\Programs\Python\Python38\lib\multiprocessing\popen_spawn_win32.py, line 93, in __init__
reduction.dump(process_obj, to_child)
File C:\Users\dspar\AppData\Local\Programs\Python\Python38\lib\multiprocessing\reduction.py, line 60, in dump
ForkingPickler(file, protocol).dump(obj)
File stringsource, line 2, in av.container.input.InputContainer.__reduce_cython__
TypeError: no default __reduce__ due to non-trivial __cinit__
Process finished with exit code 1
윈도우에서 동작하게 만들기 위해서는 단순히 VideoReader 클래스의 open 함수를 run 함수 내부에서 쓰기만 하면 된다.
위의 에러메시지 중 마지막 트레이스 백을 보면
ForkingPickler(file, protocol).dump(obj) 라는 메시지가 나타난다.
파이썬에서 multiprocess.Process의 init() 함수 안에서 호출되는 모든 함수들과 변수들은 직렬화가 가능해야한다. 특히나 multiprocess.Process의 서브 클래스를 만들고자 하는 경우 multiprocess.Process.start() 메서드가 불리기 전에 서브클래스의 인스턴스는 반드시 직렬화가 가능해야 한다.
init 함수 안의 av.open함수가 리턴하는 input_container가 아마도 직렬화가 되지 않기 때문에 VideoReader가 생성되고 나서 run함수에 들어가게 되면 문제가 발생하는 것으로 보인다.
정말로 직렬화의 문제인지 알아보기 위해 직접 pickle을 해보았다.
아래 그림에서 볼 수 있듯 container는 pickable 하지 않다.
av.open함수가 리턴하는 container 객체는 picklable 하지 않다.아무래도 PyAv는 C++ 라이브러리인 ffmpeg의 라이브러일 뿐이라 C++ 라이브러리 자체에서 직렬화가 가능하도록 코딩이 되어있지 않으면 직렬화가 되지 않을 것이다. 순수 파이썬 원시 타입을 이용해서 만들어진 데이터 타입도 아닐 것이다.
그런데 왜 우분투에서는 되고 윈도우에서는 실행이 안될까?
우분투에서는 Process의 init함수 내에서 직렬화 불가능한 객체를 허용해주는 것일까? 우분투에서만 직렬화가 되있도록 로우 레벨에 구현이 되어있을까?
먼저 우분투에서만 직렬화 되게 구현을 한 것은 아니다. 우분투 파이썬에서 똑같이 돌려보았을 때 여전이 unpicklable 하였다. 그도 당연한 것이 크로스 플랫폼을 위해 중간 언어를 쓰는 인터프리터가 OS에 따라 객체 직렬화 가능여부를 달리한다면 이는 시스템이 일관적이지 못한 것이다.
우분투와 윈도우에서 multiprocessing을 할 때 가장 큰 차이는 프로세스 생성 방식이 fork이냐 spawn이냐 이다. 이 생성 방식이 원인을 찾는 실마리가 될 것 같다.
우분투에서 메인 함수에 multiprocessing.set_startmethod(spawn)으로 할 경우 우분투에서도 같은 에러 메시지를 볼 수 있다.
에러 트레이스 백을 보면 아래 모듈에 작성된 forkingPickler 클래스가 보인다.
PyAv는 ffmpeg의 C++의 wrapper를 만들기 위해 cpython을 사용하였을 것이다. forkingPickler의 코드를 보다보면 _winapi 일 때 분기를 해놓은 것을 볼 수있다.
cpython/reduction.py at master · python/cpython · GitHub
결론을 내리자면, multiprocessing.Process는 모든 것이 직렬화 되어야 하는데 cpython으로 ffmpeg을 wrapping한 PyAv는 cpython이 윈도우 일 경우 c++ 클래스의 직렬화 구현이 되어있지 않아 생기는 문제라고 생각한다.