관련글 ▼
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(3) 일급함수-1,2
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(4) 일급함수-3,4
[인프런 리프 2기] 11. 파이썬 중급 과정 4주차(1) 병행성
병렬성(Parallelism) 이란? 여러 컴퓨터가 여러 작업을 동시에 수행하며 취합은 한 곳에서 한다 -> 속도가 장점.
강의 듣기 전 Futures의 정의에 대한 내 예측: 이름이 futures인 만큼 단순한 동시성이 아니라 앞으로 벌어질 일에 대한 가정을 두고 코딩을 하는 걸 의미하는게 아닐까?
-> 네, 아니었습니다.
요약: 비동기 작업을 실행한다.
동기는 말 그대로 동시에 일어난다는 뜻입니다. 요청과 그 결과가 동시에 일어난다는 약속인데요. # 바로 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 합니다.(출처: https://private.tistory.com/24 [공부해서 남 주자])
동기와는 반대로 요청과 결과가 동시에 일어나지 않는다는 뜻.
지연시간(Block) CPU 및 리소스의 낭비를 방지한다. 따라서 (File)Network I/O 관련 작업에서는 동시성 활용을 권장한다. 비동기 작업과 적합한 프로그램일 경우 압도적으로 성능이 향상된다.
왜? 한번에 여러 작업을 하니까.
파이썬에만 있는 특성으로 두 개 이상의 스레드가 동시에 실행될때 하나의 자원을 액세스하는 경우 문제점을 방지하기 위해 GIL이 실행된다. 즉, 리소스 전체에 lock이 걸린다. -> context switch(문맥 교환, 작업끼리 데이터를 이어받아 합침) 비용이 듬.
따라서 스레드를 많이 쓴다고 좋은게 아니다.
GIL을 우회하는 법 : 꼭 필요한 작업은 멀티프로세싱을 사용, CPython을 사용. 프로그램에 적합한 로직을 보고서 멀티 프로세싱이 필요하면 프로세싱을 쓰고 단순한 사칙연산 정도면 스레드를 쓰는게 좋다.
비동기 실행을 위한 API를 고수준으로 작성해서 사용하기 쉽도록 개선한 모듈이다.
from concurrent.Futures
장점: 과거에는 따로따로 코딩했어야 했던 멀티스레딩/멀티프로세싱의 API가 통일 되어서 매우 사용하기 쉽다. 실행중인 작업 취소, 완료 여부 체크, 타임아웃 옵션, 콜백함수 추가, 동기화 코드 등을 매우 쉽게 작성이 가능하다. -> Promise 개념
import os
import time
from concurrent import futures
WORK_LIST = [10000, 100000, 1000000, 10000000]
WORK_LIST 안에 있는 숫자들까지의 range를 전부 합산해보자.
def sum_generator(n):
return sum(n for n in range(1, n+1))
def main():
# Worker Count
worker = min(10, len(WORK_LIST))
# 시작 시간
start_tm = time.time()
# 결과 건수
with futures.ThreadPoolExecutor() as executor:
result = executor.map(sum_generator, WORK_LIST)
# 종료시간
end_tm = time.time() - start_tm
# 출력포맷
msg = '\n Result -> {} Time : {:.2f}s'
# 최종 결과 출력
print(msg.format(list(result), end_tm))
# 실행
if __name__ == '__main__':
main()
찾아보니 보통 실행문은 이렇게 작성하는 것 같다.
worker = min(10, len(WORK_LIST))
# 시작 시간
start_tm = time.time()
...
# 종료시간
end_tm = time.time() - start_tm
with futures.ThreadPoolExecutor() as executor:
result = executor.map(sum_generator, WORK_LIST)
위에 작성한 프로그램을 스레드와 프로세스로 실행했을 경우의 CPU 이용율 비교.
P는 ProcessPoolExecutor이며 T는 ThreadPoolExecutor로, process 쪽이 이용율이 훨씬 올라간다. 왜? 직접적으로 CPU 프로세싱을 돌리니까.
import os
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, wait, as_completed
패키지나 모듈은 통째로 가져오기 보다 쓸것만 가져오는 게 좋다.
아까 작성한 WORK_LIST의 값을 더 크게 바꾸면 각 자료마다 호출한 작업에 걸리는 시간이 조금씩 다를 수 있고 모든 작업이 성공할 리도 없기 때문에 그런 것들을 await과 as_completed가 제어한다.
def sum_generator(n):
return sum(n for n in range(1, n+1))
def main():
# Worker Count
worker = min(10, len(WORK_LIST))
# 시작 시간
start_tm = time.time()
여기까지는 1번 예제에서 작성한 것과 동일하다. 관건은 그 다음에 선언하는 변수이다.
def main():
# Worker Count
worker = min(10, len(WORK_LIST))
# 시작 시간
start_tm = time.time()
# futures를 받는 변수 선언
futures_list = []
# 결과 건수
with ProcessPoolExecutor() as executor:
for work in WORK_LIST:
# future만 반환할뿐
future = executor.submit(sum_generator, work)
# 스케줄링
futures_list.append(future)
# 스케줄링 확인
print('Scheduled for {} : {}'.format(work, future))
print()
futures_list 라는 빈 리스트 객체를 만든 다음 for문을 이용해 그 안에 필요한 값들을 넣는다.
# 결과
result = wait(futures_list, timeout=7)
# 성공
print('Completed Task : ' + str(result.done))
# done : 성공한 값.
# 실패
print('Pending ones after waiting for 7 secs : ' + str(result.not_done))
# not_done : 실패값
# 결과 값 출력
print([future.result() for future in result.done])
위지는 for문 내부가 아닌 with문 내부로 result라는 값을 선언해준다.
timeout 옵션 : 할당한 시간 안에 마무리 짓지 못한 작업은 실패로 간주, 강제종료 시킨다.
# 결과출력
for future in as_completed(futures_list):
result = future.result()
done = future.done()
cancelled = future.cancelled
print('Future Result : {}, Done : {}'.format(result, done))
print('Future Cancelled : {}'.format(cancelled))
wait()를 사용했을 때보다 훨씬 간략하게 작성할 수 있다.
< 강의 출처 - 인프런 우리를 위한 프로그래밍 파이썬 중급 (Inflearn Original) >
[인프런 리프 2기] 14. 파이썬 중급 과정 후기 (0) | 2021.04.05 |
---|---|
[인프런 리프 2기] 13. 파이썬 중급 과정 4주차(3) 최종실습 (0) | 2021.04.03 |
[인프런 리프 2기] 11. 파이썬 중급 과정 4주차(1) 병행성 (0) | 2021.03.31 |
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(4) 일급함수-3,4 (0) | 2021.03.28 |
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(3) 일급함수-1,2 (0) | 2021.03.27 |
댓글 영역