졸업작품에 Multiprocessing 라이브러리를 사용할 부분이 생겨서 다시 공부할 겸 정리를 해보려고 한다.
Multiprocessing 라이브러리를 공부하기 전 프로세스와 스레드의 개념을 정리하고 공부를 진행하겠다.
프로세스 vs 스레드
운영체제 공부를 하면서 프로세스의 정의는 "실행 중인 프로그램"이라고 했던 기억이 난다. 각 프로세스는 프로세스의 상태를 기록하는 프로세스 제어 블록(PCB - Process Control Block)이 존재하는데, 프로세스가 프로세서에 할당될 때마다 해당 프로세스의 제어 블록들이 먼저 적재된다. 이 과정을 문맥 교환(Context Switch)라고 한다.
스레드의 정의는 "프로그램 내에서 실행되는 흐름의 단위"이다. 한 개의 프로그램을 여러 개의 스레드로 정의할 수 있고, 이러한 방식이 멀티스레드이다. 프로세스의 직접 실행 정보를 제외한 나머지 프로세스 관리 정보, 자원과 메모리를 공유하기 때문에 시스템 성능이 향상될 수 있다.
쉬운 말로 비유해보자
"공부"를 프로그램에 비유하면
"손으로 하는 필기"와 "눈으로 보고 습득"은 두 개의 스레드가 되고
공부를 시작하면 프로그램 "공부"를 "공부 프로세스"라고 부를 수 있다.
우리의 뇌를 프로세서에 비유할 수 있고, 멀티 프로세서 환경이라고 가정하면
한 개의 프로세서에는 "손으로 하는 필기"를 다른 프로세서에는 "눈으로 보고 습득"을 실행하여 멀티 스레드가 가능하다.
파이썬의 병렬 프로그래밍
파이썬에서 멀티스레드를 지원해주는 라이브러리가 대표적으로 두 종류 있다. Threading 모듈과 multiprocessing 모듈이다. Threading 모듈은 파이썬의 GIL(Global Interpreter Lock)이라는 잠금장치가 있어서 I/O 작업이 아닌 CPU 작업이 많을 경우 오히려 성능이 떨어지게 된다. Lock을 풀고 스레드를 교환하고 다시 Lock을 거는 멀티스레드 방식이 적용되기 때문이다. 파이썬에서도 Thread 보다 multiprocessing 사용을 권장하고 있기 때문에 이 모듈에 대한 공부는 다음으로 미루고 multiprocessing 모듈에 대해 공부를 할 것이다.
파이썬의 multiprocessing 모듈은 스레드가 아닌 프로세스를 만들어주는 라이브러리라고 생각하면 편할 것 같다.
예제 코드를 통해 확인해보자.
# -*- coding: utf-8 -*-
import time
import os
import multiprocessing
semaphore = 0
def func(x):
global semaphore
while semaphore:
pass
semaphore = 1
print("프로세스 id : ", os.getpid())
print("process_list : ",x)
semaphore = 0
time.sleep(2)
return
if __name__ == '__main__' :
start = time.time()
process_list = ['p1','p2','p3','p4']
for _ in process_list:
func(_)
end = time.time()
print('sequence programming : %s sec' %(end - start))
start = time.time()
p = multiprocessing.Pool(processes=4)
p.map(func,process_list)
p.close()
p.join()
print('parallel programming : %s sec' %(time.time() - start))
소요 시간을 비교해보면 거의 1/4으로 단축된 결과를 볼 수 있다. 유심히 봐야 하는 점은 sequence programming에서 프로세스 id가 하나이고 multiprocess programming(parallel programming)에서는 프로세스 아이디가 4개 인 것이다.
semaphore 변수는 운영체제의 잠금장치 알고리즘을 구현한 플래그이다. (그냥 출력을 예쁘게 하고 싶어서 추가했다.)
다음 네 줄의 코드는 병렬 프로그래밍을 구현하는 코드이다.
p = multiprocessing.Pool(processes=4) - Pool 객체를 생성하여 프로세스를 4개로 지정해준다.
p.map(func, process_list) - Pool에 함수(func)와 인자(process_list)를 맵핑해준다.
p.close() - 자원 낭비를 사전에 막기 위해 close를 호출한다.
p.join() - 작업 완료를 대기한 후 소요 시간을 출력하기 위해 join 함수를 호출한다.
보통 웹크롤링으로 병렬 프로그래밍의 성능을 비교하기 때문에 나도 한번 구현해 보았다.
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
import requests
import multiprocessing
import time
header = {
'Referer': 'http://prod.danawa.com',
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
def craw_func(x):
response = requests.post(url = x,headers = header)
soup = BeautifulSoup(response.text, 'html.parser')
price = soup.select('.lowest_price .lwst_prc .prc_c')[0].get_text()
name = soup.select('.top_summary .prod_tit')[0].get_text()
print(name, ' : ',price,'원 입니다.')
urls = [
"http://prod.danawa.com/info/?pcode=5937666&cate=1131326",
"http://prod.danawa.com/info/?pcode=5941995&cate=1131326",
"http://prod.danawa.com/info/?pcode=6460148&cate=1131326",
"http://prod.danawa.com/info/?pcode=6460155&cate=1131326",
"http://prod.danawa.com/info/?pcode=6168338&cate=1131326",
"http://prod.danawa.com/info/?pcode=6168351&cate=1131326",
"http://prod.danawa.com/info/?pcode=5680228&cate=1131326",
"http://prod.danawa.com/info/?pcode=5680241&cate=1131326",
]
if __name__ == '__main__':
start = time.time()
p = multiprocessing.Pool(processes=4)
answer = p.map(craw_func,urls)
p.close()
p.join()
print('%s초'%(time.time() - start))
'''
test this
'processes = 4' vs 'processes = 1'
'''
processes 옵션을 1과 4로 변화를 주면서 비교해 본 결과이다.
매우 성공적!
Multiprocessing 라이브러리의 Process와 Queue객체를 이용한 병렬 프로그래밍 방법도 존재하는데 이는 다음번에 사용할 일이 있으면 따로 정리해서 공부해보려고 한다.
오늘 공부는 여기까지!
소스코드는 여기를 참고!
'Computer_Language > Python' 카테고리의 다른 글
[Python] 멀티 스레드 소켓 서버 구축하기 (0) | 2019.11.18 |
---|---|
[Python] 파이썬 소켓 서버 프로그래밍 (0) | 2019.11.17 |
[Python] 파이썬 eval 과 exec 함수에 대하여 (0) | 2019.10.04 |
[Python] 파이썬 리스트와 튜플의 차이 (0) | 2019.09.05 |