threading lock 시 어떤 함수를 사용하는게 적절할지에 대한 고민
msi 설치파일을 서버로부터 다운로드를 받고 다운받은 파일을 실행할지 여부를 묻는 팝업을 띄우는 작업을 해야했다. Z
팝업을 거절할 경우에 주기적으로 의사를 물어보아야 하기 때문에 thread 2개로 작업을 진행했고, 그 과정에서 msi 파일을 두개의 쓰레드에서 접근을 해야하는 상황이 되어서 lock 을 사용해야만 했다.
일단 본인은 lock 을 threading.lock() 객체를 이용했다.
lock_for_msi = threading.Lock()
def thread_for_download_msi():
...
with lock_for_msi:
install_msi()
def thread_for_install_msi():
...
with lock_for_msi:
download_msi()
threading.Lock() 객체는 .acquire() 와 .release() 메소드를 사용해서 lock 을 관리할 수도 있다. 하지만 lock 을 해제해주는 작업도 포함이 되어야 했기 때문에 python 의 컨텍스트 기능을 활용했다.
또, 이 스레드들은 설정된 시간동안 thread 가 block 되어야 했다. 이 경우에는 server 를 종료할 때 해당 thread 들도 break 를 해 주어야지 문제가 발생하지 않았다. 따라서 signal 을 받아서 block 을 해제해주며 break 를 해야하는 기능까지도 필요한 상황이었다.
이 경우에도 lock 이 필요했지만, 이번에는 특정한 파일에 대한 여러 자원의 접근적인 측면이 아닌 이벤트를 감지해서 해당 사건이 발생할 경우 풀어주는 기능이 중요했기에 threading.Event() 로 block 을 걸어주었다.
def thread_for_download_msi():
while True:
if now - last_update >= period:
...
finally:
if thread_stop_event.wait(1DAY): break
def on_signal(code, frame):
thread_stop_event.set()
raise KeyboardInterrupt()
signal.signal(signal.SIGTERM, on_signal)
signal.signal(signal.SIGINT, on_signal)
이 경우엔 서버로부터 종료 시그널이 보내질 경우 set 이 wait 하고 있는 threading.Event 가 True 로 변환되며 thread 가 자동으로 종료된다.
수동적인 lock 을 제공하는 개념, 외부에서 lock 에 signal 을 보내 해제하는 방법 모두 매력적인 방법이었다. 이 두 기능을 전부 가진 threading.Condition 함수도 존재를 했다.
이 함수는 기본적으로 .Event 처럼 해당 스레드를 wait 하게 할 수 있다. 그런데 다른 thread 에서 notify, notify_all 등의 방법으로 그 wait 을 해제시킬 수도 있다.
동기식과 비동기식
https://yeon-lee.tistory.com/177
[CS] call back 이란, 동기식과 비동기식
콜백 함수 란 콜백 함수는 나중에 수신 함수에 의해 호출될 것으로 예상되며 다른 함수에 인수로 전달되는 함수 입니다. 이 방식은 비동기식 혹은 이벤트 구동 함수에서 사용된다. 콜백 함수는
yeon-lee.tistory.com
asyncIO 가 multithread 환경에 적합하지 않은 이유
singlethread 가 왔다갔다 하면서 일하는 방식이라서 그렇다.
multithread 도 파이썬에선 어차피 GIL 문제때문에 싱글스레드와 성능이 비슷한데 여기서 asyncIO 까지 쓰면 contextswitch 비용만 더든다.
gil 임에도 멀티쓰레드를 쓰는 이유는 IO 에 효율적이고 가독성이 높아지기 때문이다
ex) consumer-producer 패턴으로 IO 를 3개 해야하는 상황이라 생각해보자. 그렇다면 메읻쓰레드는 정신없이 asyncIO 를 해야할 것이다. 하지만 쓰레드를 쓴다면 코드의 가독성이 훨씬 높아진다.
list comprehension > generator
메모리 사용량을 줄이고, CPU 연산을 효율적으로 잡기 위해 나온 개념이다.
list comprehension 의 예는 다음과 같습니다.
list = [x*x for x in range(10)]
# list = [0, 1, 4, 9, 16, 25, ...]
list_1 = [x for x in list if x%2==0]
# list_1 = [0, 4, 16, 36, 64, ...]
가독성을 약간 떨어뜨리는 대신 불필요한 if 문 없이도 간결하게 표기할 수 있었는데요
여기서 문제는 연산이 복잡하다면 컴퓨팅 자원을 많이 사용하고
리스트가 길다면 메모리 공간을 많이 차지한다는 단점이 있습니다.
이때 나온 개념이 generator 입니다
squares_list = [x ** 2 for x in range(1, 11)]
print(squares_list)
# 리스트로 계산할 경우 list 로 저장되어 메모리를 누수함과 동시에 필요하지 않을 때 계산을 모두 함
squares_generator = (x ** 2 for x in range(1, 11))
for square in squares_generator:
print(square)
# 제너레이터로 계산할 경우 메모리를 추가로 차지하지 않으며 필요할 때만 계산을 함
def countdown(n):
while n > 0:
yield n
n -= 1
# countdown 함수를 호출하면 제너레이터 객체가 생성됩니다.
# 이 객체를 이용하여 값을 생성하고 호출자에게 반환합니다.
countdown_generator = countdown(5)
# 값을 하나씩 생성하고 출력합니다.
for value in countdown_generator:
print(value)
generator 를 사용한다면 필요한 연산을 그때그때 수행하는 것이 가능하게 되고, 기존에는 floating point 가 무한대로 내려가서 계산하지 못한 무한 연산과 같은 것이 가능하게 됩니다.
'개발' 카테고리의 다른 글
[wix] flask 프로젝트 윈도우 패키징 (1) | 2024.09.13 |
---|---|
[Flask] Frontend 템플릿 Jinja 구조 (0) | 2024.07.25 |
[Flask] flask backend 템플릿 구조 (0) | 2024.07.24 |
[WIX] Major upgrade, Minor upgrade 조건과 차이 (2) | 2024.07.24 |
[Window] win32api, Microsoft installer (0) | 2023.10.12 |