본문 바로가기

개발/Python & Flask

Flask Python Threading 쓰레딩에 대해 알아보잣

언제나 개발중

 

 

서론

문제점 : 두개의 run() 동시 실행 불가

파이썬 빌트인 라이브러리이며 많이 쓰이는 sched.scheduler 모듈은
몇초 뒤에 실행될 모듈을 enter 함수를 통해 입력하고
마지막에 run 을 통해 일괄적으로 수행한다 ( 수행 중에도 계속 enter 로 입력받을 수 있다.)

 

Flask 또한 마지막에 app.run 을 통해 서버를 구동한다.

 

나는 어떤 작업이 반복적으로 실행되면서, 서버를 구동시켜야 하는데,

이 두개의 run 은 일반적으로 동시실행 할 수 없다.

 

어느 한 부분에서 run 을 호출하면 그 라인에 멈춰서 그 부분만 실행하게 된다.

어떻게 해결할 수 있을까!?


(시행착오 스토리를 읽고 싶다면 클릭)

더보기

보통 스케줄링은 scheduling 라이브러리를 별도로 받아서 한다.

 

하지만 우리 회사는 철저한 망분리 보안정책으로 인해 개발망 컴퓨터는 오프라인 환경이었고,

별도의 라이브러리를 추가하려면 인터넷망에서 zip형태로 다운로드 받은 후

결재받고 또 옮기고 서버에 설치하고 전산망에 설치하는 작업은 더이상 Naver...

 

다음 알게된 라이브러리는 multiprocessing 인데, 어찌해도 이 것으론 실행할 수 없었다.
Process, Pool, Queue 등 배워야할 개념도 엄청나고,

뭔가 그런 그... 굳이 필요없는 고오급 기술이었다고나 할까...

sched 는 잘 쓰는 사람이 없어서 정보량도 적었다.

 

꼼수를 총동원해서 갖은 시도를 다 해봤다.
app.run 을 sched.scheduler enter 로 넣어보기도 시도하고,
Process (target = app.run) Process(target = s.run) 도 해보고,
Process(target = app.run) 만 넣고 s.run 은 그냥 실행도 해보고,
바꿔서도 해보고,

 

그냥 다 안됐다.

TypeError: can't pickle _thread.lock objects

요로코롬 화려한 에러가 날 감싸더라...

 

 

결정적으로 Process(target = app.run, args = ") 해서 Argument 를 넣어야하는데,
Argument 는 변수의 집합만 발지,

app.run(host = "0.0.0.0") 과 같이 (변수이름 = 변수) 와 같은 형식의 파라미터는 지원하지 않았다.

app.run 이 있는 run.py 를 쫓아들어가서 default 값 자체를 수정하기도 해봤는데 그것 역시 먹통.

한시간 가량의 삽질 결과, 해결책을 찾아낸다.

 


Threading 라이브러리

From threading import Thread

def init();
	t1 = Thread( target = s.run )
	t1.daemon = True
	t1.start()

이 5줄로 그냥 끝났다.
여기서 daemon 속성은, 메인 쓰레드가 종료될 시 같이 종료되는 종속성을 가지느냐 여부이다.

이 것을 True 로 설정하지 않으면 메인쓰레드를 죽여도 서브쓰레드가 할 일이 있으면 계속 돌아간다.

 

개인적 평가:

장점: 간편함. 내장 라이브러리.

단점: Thread 자체를 출력해보면, 부여된 고유 id가 출력되는데, 같은 프로그램 내에서 같은 생성함수를 여러번 호출해도 매번 새로운 id가 부여된다. 고로 호출해야 하는 쓰레드가 많지 않을 때 사용할 것, 반복적으로 같은 기능을 하는 쓰레드를 호출(=생성)할 때 유의해야겠다.

 

Threading 와 Multiprocessing 의 차이:

Threading 메모리를 공유해서 상태 공유가 쉽다.
GIL① 을 이용한 병렬처리 가능
I/O bound② 애플리케이션에 옵션이 많다
interrupt/kill 불가능
command queue③/message pump④ 모델을 따르지 않는 경우 동기화필요.
race condition 이 발생할 수 있다.
Multiprocessing
(Threading 라이브러리를 기반으로 여러 프로세스의 작업을 나누는 기능을 제공한다.)
메모리를 공유하지 않는다. 코드흐름이 명확하다.
Multi core/CPU 의 장점을 쓸 수 있다. 
자식 프로세스를 kill/interrupt 할 수 있다. 
다양한 interface 를 사용할 수 있다. 
메모리를 많이 먹는다. 
IPC(Inter Process Communication)⑤ 에서 오버헤드가 더 크다.

출처링크

 


결론

기능구현을 위해 Threading을 선택했지만, 개발중인 시스템에게 더 적합한 라이브러리는 뭘까?
개발중인 환율시스템 자체가 몸집이 크지 않고, 여타 시스템에 붙을 서브시스템이기 때문에.
메모리를 적게 먹고 환율시스템 규모에 적당하게 심플한 Threading을쓰는 것이 더 어울리지 않을까 라는 생각을 한다.
만약 더 복잡하고 추후 기능들이 추가될 수 있는 시스템이었으면 multiprocessing 을 쓰는 것이 나을 것 같다.


주석:

  1. GIL : Global Interpreter Lock. 파이썬에서 쓰레드를 동기화 문제를 해결해주는 매커니즘이다. 여기서 말하는 인터프리터는 파이썬 인터프리터를 말하고, 락은 프로세스를 처리할 쓰레드가 하나씩 접근해 경합상태를 방지하기 위한 Lock 을 말한다. 파이썬 인터프리터는 근본적으로 한번에 한 쓰레드만 바이트코드를 실행하도록 제한해두었기 때문이다.
    출처: 아주 잘 정리된 블로그 링크
  2.  I/O bound : CPU 스케줄링에서 프로세스의 우선순위를 결정할 때 따지는 조건중 하나. I/O Bound process는 데이터의 인풋 아웃풋이 끝나기까지 기다려야하는 프로세스. 그의 반대개념인 CPU Bound process는 CPU를 많이 사용하는 프로세스를 말한다. I/O Bound가 데이터 인풋 아웃풋을 기다려서 처리시간이 오래걸리고, 컴퓨터의 성능도 어느정도 저하되지만, CPU사용시간이 짧기 때문에 일반적으로 I/O Bound 가 우선적으로 처리된다.
    출처: 아주 잘 정리된 블로그 링크
  3. command queue : 이름 그대로 명령 대기열이다. 명령들을 대기열에 집어놓고 하나씩 나오게 하고, 이 제어를 통해 CPU를 다른 곳에 더 사용하는 등 목적을 달성한다.
    출처: 위키피디아
  4. message pump : 한 스레드에게 긴 시간이 걸리는 작업(for / while 루프 등)을 그냥 실행시키면, 그동안 사용자의 입력을 받지 못하는데, message pump를 사용하면 작업 도중에 사용자가 입력을 할 수 있게 된다.
    출처: 아주 잘 정리된 블로그 링크
  5. IPC(Inter Process Communication) : 프로세스 간 통신. 여러개의 프로세스가 돌아갈 때 타 프로세스의 주소공간을 침범(write)하면 안되기 때문에, 프로세스는 철저하게 분리되어 각자의 메모리, 각자의 주소를 가진다. 하지만 프로세스 간 데이터를 주고받으며 통신해야할 일이 분명 있는데 (센서로부터 읽은 값을 타 프로세스에게 전달하거나, LCD에 출력할 메시지를 전달하는 등), 이 것을 IPC라고 한다. 대표적으로 1. 시그널, 2. 파이프, 3. 메시지큐 등이 있다.
     - 시그널 (리눅스 Ctrl+C 와 같이 신호를 보내는 것), 파이프 (cat a.txt | grep someword), 메시지 큐(자기 주소공간의 메시지를 메시지큐 주소공간으로 복사하고, 메시지큐 주소공간에서 대상 주소공간으로 복사하여 데이터를 전달) 출처: 아주 잘 정리된 블로그 링크

 

 

 

 

도움이 되셨다면 ♡공감 ↓광고클릭으로 저를 응원해주세요!!! :D

반응형