파이썬과 동시성


*본 글은 대략 동시성 프로그래밍에 대해서 알고 있는데 파이썬은 시작 단계이며 어떤것들이 있는지 빠르게 훑어보고 싶은 분들을 위해 눈높이가 맞추어져 있음을 알려드립니다. 기본 쓰레드에서부터 시작해서 전반적으로 살펴볼 것 이나 버전별 차이는 다루지 않고 있습니다. 해당 키워드에 대한 버전별 구분 및 세부적인 튜토리얼은 구글링을 통해 찾아보시길 바랍니다. (참고 링크는 추가해두었습니다)


리액티브 및 동시성이라는 화두가 파도치고 있는 요즘 파이썬도 뒤떨어 질 수는 없겠지요? 데이터분석이나 쉘스크립팅의 대안정도로만 생각했던 분들도 계실것이지만 파이썬도 다양한 기술들을 제공하며 서버개발의 메인 언어로서 그 대열에 함께 하고 있습니다. 개인적으로는 동시성에 대한 추상층 api 지원에 있어서 파이썬은 스칼라,Go 정도까지는 아니더라도 자바, C++, 자바스크립트 정도와는 대등한 만큼의 언어/라이브러리적 지원 및 발전을 하고 있는 것으로 느껴집니다. 본격적으로 시작하기 전에 동시성,병렬성,분산등에 대한 정의부터 내리고 시작하겠습니다.


동시성이 무엇인지 병렬성이 무엇인지는 딱히 규정된것은 아니며 사람마다 조금씩 다르게 규정하기도 합니다.
여기서는 저의 정의(혹은 제가 참고한) 라는 점을 알려드립니다. 



동시성

이것은 구성의 문제입니다. 즉 여러가지의 일들이 어떤식으로 전개되어 있느냐, 어떻게 구조화 할 것이냐 에 관한 내용입니다. 즉 a 라는 일과 , b 라는 일이 같이 어울어져 발생하며 어떻게 그 일에 대한 순서를 블럭킹/시퀀셜하지 않게 만드느냐에 대한 문제입니다. 현실적으로는 가장 많이 사용되는 것은  I/O 가 일어나는 곳이구요.

위의 그림은 스칼라의 Future 설명을 하기 위한 포스팅에서 가져온 그림인데요.  
메인쓰레드가 흘러가는 와중에 다른 일들이 그림 상단에서 벌어지고 있으며, 일들이 완료 되는 순간 다시 합쳐지고 그러한 일들이 구성되어 있습니다. 원격웹서버를 통해 i/o 처리도 하고 있구요.


이러한 구성은 보통 로우레벨에서 멀티플렉싱/ 비동기입출력 / 쓰레드 / 프로세스등을 사용해서 처리합니다. 우리가 알 필요가 없는 부분입니다. 근데 말입니다. 아래 살펴 볼 병렬성도 쓰레드와 프로세스등을 사용하는데요 즉 쓰레드와 프로세스를 사용하는것은 어느쪽에서든 도구로 이용된다는 뜻입니다. 그 자체로 구분의 지표가 될 수 없다는 말. 


각 언어/라이브러리마다 이러한 동시성에 대한 구성을 단순화 시켜주기 위한 Future, Promise, async, goroutine, Observable, Iteratee , deferred , Akka 등 다양한 기술들이 제공되고 있습니다.


그냥 얘기하나 해볼께요. (동기와 비동기에 대한 이야기입니다)

우체국이 있습니다.
저는 소포를 보내려는 손님이죠.
손님들 100명이 우체국에 일렬로 줄을 서있고, 한명씩 처리하는것이 -> 싱글스레드 / 동기 처리입니다.
손님들 100명이 우체국의 100명의 직원에게 각각 처리하는 것이 -> 멀티쓰레드 / 동기 처리입니다.

여기까진 명쾌하죠. 
그럼 손님들 100명이 우체국에서 번호표를 받아서 각각 자기 할일 하다가, 우체국에서 스마트폰으로 니 차례야 하고 알려주면 그 때 우체국에 순간이동(할 수 있다고 합시다)해서 소포를 보낼 수 있을 만큼 보내는것은??

네 이것은 싱글쓰레드 / 동기 처리입니다. 동기? 왜 동기 일까요? 사실 이것을 비동기로 봐도 되긴 합니다.
왜냐면 손님은 일단 기다리지 않고 (블럭되지 않고) 자기 할일을 할 수 있기 때문에, 비동기로 봐도 됩니다.
다만 하마님아~~~ 님 차례 됬다. 라고 알려주면 하마는 자기 소포를 보내게 되는데! 바로 이 순간은 동기의 순간입니다. 즉 내가 너무 많은 소포를 보내면 , 우체국은 다른 사람들에게 니 차례야 라고 말해줄 수 없는거에요. 병목이 생긴다는 의미입니다. (이게 Windows 에서 SELECT 이자, Java NIO 이며, Node.js 가 왜 비지니스로직이 길면 문제가 생기는지에 대한 대답입니다. 또한 node 나 자바,파이선의 selector 가 비동기 방식이라고 말하고 있는데 생각하기 나름입니다. 하이레벨에서의 구분이냐? 로우레벨에서의 구분이냐에 따라서 달리 볼수 있기 때문에~) 

하지만 
손님들 100명이 우체국 뒷마당에다가 소포를 던져두고, 이벤트 알림표를 받고 집에가서 자기 할일을 합니다.
그러다가 우체국에서 알림이 오겠죠. "니 소포 다 보냈어" , 다른 손님에게도 알림이 갑니다. "니 소포도 다 보냈어"  자~~~ 이렇게 되면 모두 병목에서 해방됩니다. 대신 우체국(OS) 가 더 많은 일을 하게 되겠죠.
이게 바로 진정한(?) 비동기 입니다. Windows 의 IOCP 입니다. 

간단하게 차이를 정리하면
- 일을 할 수 있는지 알려주는 방식 (react)
- 일의 완료를 알려 주는 방식(proact)

입니다. 

초기 각 언어의 라이브러리들은 반동기식(?)의 i/o를 지원해줬었고, 이제 몇몇 라이브러리들은 Proactor 패턴식의 비동기 I/O 입출력도 지원해 주기 시작했습니다. 파이썬도 EpollSelector / KqueueSelector / SelectSelector 등 지원함.



병렬성

이것은 계산의 문제입니다. 동일한 시간에 (동일하다는 기준은 조금씩 달라짐) 여러 계산들이 동시에 이루어 진다는 느낌입니다. 예를들어  1부터~1억을 더하는데 , 하나의 쓰레드에서 모두 더 할 수도 있지만, 10개의 쓰레드에서 양을 나누어서 계산한 후 최종적으로 더할 수도 있을 것입니다. 그때 10개의 쓰레드는 병렬성을 가지고 있는 것입니다. (병렬이 어느 수준으로 이루어지느냐는 별개) 

방법으로
1.일반 쓰레드를 사용하는 방식
2.일반 프로세스를 사용하는 방식
3.여러 컴퓨터에 분산해서 사용하는 방식 
4. GPGPU 를 통한 방식 (그래픽카드의 수만개 이상의 쓰레딩모듈) 
5. CPU 를 통한 방식 (SIMD 명령방식 : 단일명령으로 다중데이터처리) 

AVX,SSE,CUDA, openMP,AMP,PPL,openCL,TBB 같은 언어 및 벤더별 혹은 표준라이브러리들이 많이 있습니다. 

GPU vs CPU 에 대한 논쟁도 있습니다.


대규모 병렬화에 적합한 흥미로운 예를 하나 들어 보겠습니다.

CUDA 를 통한 Volumne Rendering 에서 가져왔는데요.



이러한 입체영상을 가시화 하기 위해서는 사람이 보는 각도에 따라서  모든 픽셀에 대한 재계산이 필요합니다. 하나 하나의 픽셀을 얻기 위해서 어떠한 수학(직선상의 매칭되는 모든 값들을 가지고 입체적 느낌을 살리기 위한 특정 중간값 계산)이 필요한데 계산량이 좀 됩니다. 이때 픽셀이 수천만개라면 마우스를 가지고 이리 저리 돌려보는 순간 일일이 하나씩 계산하고 있을 순 없자나요? 수만개 이상의 쓰레드가 동시에 계산합니다. 계산하는데 다른 픽셀의 값이 필요하지 않기 때문에 완전병렬이라고도 합니다. 물론 파이썬으로도 구현됩니다.



파이썬에서의 동시성


멀티 쓰레드


이제 파이썬(CPython 기준)에서의 동시성에 대해 알아보도록 하죠.  먼저 쓰레드를 살펴봅시다. 우리 모두가 알고 있는 그 쓰레드 입니다. 다만 파이썬에서 특이사항은 파이썬은 GIL이라는것이 존재합니다. 이것은 한 순간에 하나의 쓰레드만 작동하도록 만드는 것인데요. 따라서 CPU,쓰레드가 여러개 일 지라도 CPU를 하나 만 사용하는것과 마찬가지입니다. 쓰레드를 해당 순간에 하나만 사용하기 때문에 락은 필요 없을거 같다고 얼핏 생각할 수 도 있으나, Lock, Event 객체들이 있으며 베타적 제어를 해주지 않으면 파이썬일 지라도 문제가 생기는 것은 마찬가지입니다.


CPU 를 하나만 사용하기 때문에, 동시에 병렬적으로 계산하는 업무에는 맞지 않습니다만 , I/O 기반의 업무라면 동시성을 통해서 충분히 고효율을 보장 받을 수 있습니다. 자 기억합시다!! 파이썬에서 쓰레드활용은 I/O , 비동기에서만 활용하자, 병렬 계산을 위해서라면 프로세스,GPU,분산을 활용하자!!


아주 간단한 예제를 보시죠.

# python 2.7 에서 테스트
import
threading
from time import sleep

def myThread(name,nsec):
print ("---- do somthing ----")
sleep(nsec)

if __name__ == '__main__' :
t = threading.Thread(target=myThread, args=("Thread-1", 3))
t.start()
t.join()
print ("---- exit ----")

threading 모듈을 이용해서 쓰레드를 myThread 함수를 통해 실행시키고 있습니다. 인자로 이름하고 몇초 동안 일을 할 것인지 알려주고 있구요.


제 경험상 대부분의 쓰레드 태스크는 i/o 를 동반하고 있습니다. 즉 위의 코드에서 myThread 에서는 보통 웹이나 TCP 소켓등을 통해 remote에 접속해서 무엇인가 가져 온다든지 하는 작업이 주를 이루고요. 그때 myThread 에서 생성 혹은 가져 온 데이터는 메인쓰레드에게 그 데이터를 전달해 줍니다. 그때 queue 를 활용하는데요.
네 표준 queue 는 내부적으로 베타제어를 하고 있기 때문에 쓰레드에 안전합니다.


queue 를 활용하는 다음 예를 보시죠.

#coding=utf-8
# python 2.7 에서 테스트
import threading
import queue
from time import sleep

def myThread(name,q):
i = 0
while True:
sleep(1)
q.put(i)
i +=1

if __name__ == '__main__' :

BUF_SIZE = 10
q = queue.Queue(BUF_SIZE)

t = threading.Thread(target=myThread, args=("Thread-1", q))
t.start()

while True:
num = q.get()
print (str(num) + " 이 생성되었습니다")


print ("---- exit ----")

queue 모듈을 임포트하여 사용했습니다. myThread 에서 큐에 값을 put 하고 메인쓰레드에서 get 해서 사용하네요. 매우 간단히 생성자-소비자 패턴이 해결 되었습니다.



멀티 프로세스

위에 언급했다시피 파이썬에서 병렬적으로 계산하는 상황에서의 멀티쓰레드는 오히려 더 성능이 안좋아 집니다.
이때 멀티 프로세스를 활용하여 해결 할 수 있는데요. (이 이유로 다른 언어보다 프로세스를 적극 활용합니다.)자 겁내지 마세요. 멀티쓰레드와 아주 흡사하게 만들 수 있습니다.

import multiprocessing
from time import sleep

def myProcess(name,nsec):
print ("---- do somthing ----")
sleep(nsec)

if __name__ == '__main__' :
t = multiprocessing.Process(target=myProcess, args=("Process-1", 3))
t.start()
t.join()
print ("---- exit ----")

똑같죠? 파이썬의 힘입니다. 너무 간단합니다. threading.Thread 를 multiprocessing.Process 로 바꾸었을 뿐입니다.그럼 둘간의 통신은 어떻게 하냐구요? 네 마찬가지로 Queue 를 통해서 할 수 있습니다. 다만 주의 하실 점은
import Queue 모듈이 아니라 from multiprocessing import Queue 모듈을 사용해야 한다는 점! 잊지마세요.




eXECUTORS 와 퓨처(fUTURE)

이제 좀 추상층을 높여 봅시다. 현재 각종 언어들 마다 쓰레드를 직접적으로 사용자가 만들어서 사용하는 것을 지양하고 있으며, 추상층을 쌓아올려서 보다 쉽게 하지만 좀 더 적극적으로 사용 할 수 있게 끔 유도하고 있는데요. 그것은 쓰레드를 직접 사용하는데에서 오는 어려움이기 때문일 것입니다. 자바의 경우 5버전부터 더그리(Doug Lea) 에 의해 강력한 동시성 라이브러리들이 추가 되기 시작했는데요. 많이들 사용하시는 Executor 과 같은 것이 파이썬에도 있습니다.

Executors / ThreadPoolExecutor / ProcessPoolExecutor

Executors 를 상속받은 2개의 구현체입니다. 이름에서 나타나듯이 하나는 쓰레드풀이고 하나는 프로세스 풀입니다. 둘은 거의 동일한 구문으로 사용되기 때문에 둘 중에 하나(프로세스풀)만 살펴보죠.

from concurrent.futures import ProcessPoolExecutor
from time import sleep


def return_after_5_secs(message):
sleep(5)
return message

if __name__ == '__main__' :
pool = ProcessPoolExecutor(3)

future = pool.submit(return_after_5_secs, ("hello"))
print(future.done())
sleep(5)
print(future.done())
print("Result: " + future.result())

역시 코드가 모든것을 잘 설명해주고 있습니다. 파이썬은 정말 위대합니다.ㅎㅎ
쓰레드 3개를 운용하는 풀을 만들어주고, 하나의 일(Task) 를 제출한 후에 바로 future 를 리턴 받습니다.

리턴 받은 future 에 진짜 값이 들어 올 때까지 대기하다가 실제 값이 있을 경우 (코드에서는 future.done() 이 True일 경우) result 함수를 호출해서 가져옵니다.  동일한 작업을 ThreadPoolExecutor 를 통해서도 쓰레드레벨로 가능합니다.


* 퓨처이야기 



비동기와 Asyncio

지금까지 것들은 주로 여러 쓰레드를 활용해서 처리하는 내용들이 었습니다. 이제 살펴 볼 것은 하나의 쓰레드를 가지고 어떻게 효율적으로 CPU 를 다룰 수 있는지에 관한 이야기입니다.(무조건 하나의 쓰레드만 사용한다 라는 것은 아닙니다)  Node.js 에 대해서 알고 있는 분이라면 쉽게 생각하실 수 있을거 같네요. 네! 비동기적으로 코드를 다루는 방법을 말하려 합니다.


파이썬은 자신은 GIL 에 의해 한번에 하나의 쓰레드 위주로 작동하지만, 로우레벨로 내려가면 GIL 을 무시하고 자체적인 I/O 실행환경을 활용하게 됩니다. 하나의 쓰레드가 I/O 작업은 OS에게 맞겨 두고 자신의 일을 하다가, OS 가 어떤 이벤트를 알려오면 ( 나 일 끝났어요~~ 등등) 그때 제어권을 살짝 바꾸어서 그 이벤트에 해당하는 일을 하게 하는 것입니다. 이렇게 되면 I/O 작업이 끝나기만을 무작정 기다리는 지고지순하지만 매우 답답한 파이썬에서 벗어 날 수 있지요. 물론 이러한 처리는 여러개의 쓰레드를 만들어서 (멀티쓰레드) 로 할 수도 있겠지만, 쓰레드를 무작정 늘리는것도 바람직하지 않다는것은 이제 다 알지 않습니까? 그렇습니다. 대세는 비동기입니다.


파이썬에서의 비동기를 알아 보기 위해서 제네레이터와 코루틴에 대해서 먼저 알아봐야 합니다. 생소한 분도 많겠지만 잠시 살펴보지요.



제네레이터

제네레이터는 말그래도 값을 생성하는 함수입니다. 알다시피 함수는 값을 반환한 다음 자신의 스코프를 소멸시키고 , 다시 함수를 호출하면, 처음부터 다시 시작됩니다. 즉 한 번 실행됩니다. 그러나 제네레이터 함수는 값을 생성하고 함수의 실행을 일시 중지 할 수 있습니다. 컨트롤이 호출 스코프로 반환되며 ,원하는 경우 실행을 다시 시작하여 다른 값 (있는 경우)을 얻을 수 있습니다. 이 예제를 보시죠.

def simple_gen():
yield "Hello"
yield "World"


gen = simple_gen()
print(next(gen))
print(next(gen))

제네레이터 함수는 어떤 값도 직접 반환하지 않고, 호출되면 반복자와 같은 제네레이터 객체를 건네줍니다. 그 후 제네레이터 객체에 대해 next ()를 호출하여 값을 반복 할 수 있습니다. 또는 for 루프를 실행하여 처리합니다.


요약 : 제네레이터 함수는 하나의 값을 반환하는 대신 실행을 일시 중지하고 여러 값을 생성 할 수있는 함수입니다.


코루틴

이제 제네레이터를 사용하여 함수 컨텍스트에서 데이터를 가져올 수 있고 실행을 일시 중지 할 수 있음을 알게 되었습니다. 근데 제네레이터에 데이터를 푸시하고 싶다면 어떻게 해야 할까요? 즉 제네레이터 자신이 계속 데이터를 만드는 것이 아니라, 외부에서 제공되는 데이터를 소비하는 역할을 하고 싶습니다. 이때 코루틴이 등장 할 때입니다. 값을 받는데 사용하는 yield 키워드는 함수 내부의 "=" 오른쪽에 있는 표현식으로 사용 할 수도 있습니다. 제네레이터 객체에 대해 send() 메서드를 사용하여 값을 함수로 다시 전달할 수 있는데요. 이를 "제네레이터 기반 코루틴" 이라고 합니다. 아래는 그 예입니다.


"코루틴은 주거니 받거니 하는 함수이다. 먼가 살아숨쉬는 듯한 녀석

좀 헥깔릴 수 있는데요. 함수와 제네레이터 , 코루틴 모두 def 를 통해 만듭니다. 이것에 대해 모두 다르게 표시해야 한다고 주장하는 사람들이 있으며, 파이썬의 창조자 귀도는 이에 반대하여 def 로 통일되어 있다고 합니다. 중요한것은 코루틴은 반복이 목적이 아닙니다. 반복이 목적인 것은 분명히 제네레이터의 역할입니다. 코루틴은 외부와의 상호 작용입니다.

def coro():
hello = yield "Hello"
yield hello


c = coro()
print(next(c))
print(c.send("World"))

제네레이터 예제에서는 제테레이터 함수가 모든 값을 생성했지만, 여기서는 보시다시피 코루틴 함수로 값을 넣어주기도 합니다. send 를 이용해서 말이죠. 


집중!!! 코루틴 함수는 값을 생성하는게 아니라~ 값을 어디선가 받는 역할이 중요한 함수입니다. 여기서는 제가 직접 send 로 값을 주었습니다만, 나중에 설명 할 비동기에서 코루틴은 요긴하게 사용되는데 잠시 생상을 해보세요. 어떻게 사용 될까요? 네 어떤 비동기 작업 (주로 i/o 많을듯) 에 대한 결과를 받아서 전달하는 매개함수 역할을 합니다. 매우 중요합니다.  



Async I/O

Python 3.4부터 일반적인 비동기 프로그래밍을 위한 멋진 API를 제공하는 새로운 asyncio 모듈이 생겼습니다. 이제  asyncio 모듈과 함께 coroutines를 사용하여 비동기 작업을 쉽게 수행 할 수 있게 됬습니다. 다음은 공식 문서의 예입니다  다시 말씀드리지만 전체 시스템이 블러킹이 안되게 하는 방법으로 첫째, 멀티쓰레드를 통해 하나만 블럭되게 한다 2. 비동기 방식을 사용한다.  이렇게 2개로 크게 볼 수 있다고 말했죠? asycnio 는 비동기 방식에 대한 이야기 입니다.

import asyncio
import datetime
import random


@asyncio.coroutine
def display_date(num, loop):
end_time = loop.time() + 50.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
yield from asyncio.sleep(random.randint(0, 5))


loop = asyncio.get_event_loop()

asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))

loop.run_forever()

결과:

Loop: 1 Time: 2017-05-01 16:15:20.801473
Loop: 2 Time: 2017-05-01 16:15:20.801473
Loop: 1 Time: 2017-05-01 16:15:21.803184
Loop: 2 Time: 2017-05-01 16:15:23.803627
Loop: 1 Time: 2017-05-01 16:15:25.804033
...


위의 코드 역시 스스로를 잘 설명해 주고 있습니다.  (저도 파이썬에서 이벤트루프가 어떻게 동작하는지에 대한 깊은 이해가 부족합니다. 먼저 WINAPI 의 이벤트루프를 떠올려 봅니다. 멀티플렉싱I/O 도 떠올려보고 자바의 NIO 도 떠올려보고 node.js 도 떠올려보고...다음 링크는 파이선과 node.js 에서의 비동기에 대한 매우 괜찮은 글입니다. 아마 node.js 비동기에 대한 글이 훨씬 더 많기 때문에 이해를 돕기 위해 node.js 에 대한 글을 찾아서 읽는것도 도움이 될 듯하네요. http://sahandsaba.com/understanding-asyncio-node-js-python-3-4.html)  

코드를 살펴보면 이 함수(display_date) 는 주어진 시간(초) 후에 완료되는 코루틴 (coroutine)입니다. 식별자 수(num)와 이벤트 루프(loop)를 매개변수로 받아 현재 시간을 계속 출력하는 coroutine 인 display_date (num, loop) 을 만듭니다. 코루틴이기 때문에 외부로 부터 값을 받아드리는 성질이 있다는 것은 예상 할 수 있겠지요?  즉 다음 asyncio.sleep () 함수 호출의 결과를 기다리기 위해 키워드yield from 를 사용했습니다. 그래서 우리는 그것에 임의의 초를 보내고 asyncio.ensure_future()를 사용하여 기본 이벤트 루프에서 코루틴의 실행을 스케쥴합니다. 그런 다음 루프가 계속 실행되도록 요청 합니다.

출력을 보면 두 개의 coroutine이 동시에 실행되는데요. yield from 를 사용할 때, 이벤트 루프는 코루틴의 실행을 일시 중지하고 다른 루틴을 실행합니다. 따라서 두 개의 코루틴이 동시에 실행됩니다 (그러나 잊지 말아야 할 것은 이벤트 루프가 단일 스레드이기 때문에 병렬로 실행되지 않습니다).

 yield from 는for x in asyncio.sleep(random.randint(0, 5)): yield x 에 대한 멋진 syntactic sugar 입니다. 그것은 비동기 코드를 좀 더 간략히 만들어 주죠. 


아래 링크는 asyncio 에 대한 훌륭하고 간략한 튜토리얼 글입니다.

https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e


아래 링크는 제가 번역한 결과물입니다. 정말 휼륭한 글이며, 번역하느라 고생 마이~했습니다.

파이썬 asyncio 를 이해 하기 위한 여정 



 ASYNC/AWAIT

이것은 위에 살펴본 asyncio 와 동일합니다. 다만 키워드가 좀 바뀌었을 뿐이에요.. 겁먹지 마세요.
위의 코드와 똑같죠? 네 async 와 awit 키워드만 바뀌었습니다. 코드에 대한 설명으로 더 적절해 보입니다.

import asyncio
import datetime
import random

async def display_date(num, loop, ): # <----- 요기
end_time = loop.time() + 50.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(random.randint(0, 5)) # <----- 요기


loop = asyncio.get_event_loop()

asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))

loop.run_forever()

아래 링크는 async/await 에 대한 훌륭하고 간략한 튜토리얼 글입니다.

https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/



gevant

asyncio 와 개념적으로 동일한 역할을 하는 비동기 라이브러리입니다.


- gevent 사용하는 방식에 대한 글이며
gevent for the Working Python Developer,

- gevent 와 asyncio 에 대한 비교글입니다.

http://youngrok.com/asyncio%EC%97%90%EC%84%9C%20gevent%EB%A1%9C

http://www.getoffmalawn.com/blog/playing-with-asyncio



celery

셀러리는 분산환경에서 동시성을 갖기 위해 제작된 라이브러리 입니다. 웹환경에서 예를 들면 클라이언트가 웹서버에 어떤 요청을 할 때 웹서버레벨에서 모든것을 다 처리하기엔 부담이 크거나, 외부의 모듈과 협업해야 할 때, 즉 비동기적으로 요청에 대한 부하를 외부에 전가시키고,완료됬다는 이벤트가 발생 했을 경우 사용자에게 응답을 날리는 구조에서 사용됩니다.

요즘 스칼라의 Play / Akka Http 나 자바의 RxJava 처럼 리액티브 스타일의 웹개발이 앞으로 많이들 활용 될 거라 생각해 볼때, 파이썬의 경우 대표적인 웹툴인 장고는 전통적인 멀티쓰레드 기반으로 알고 있는데 파이썬은 어떻게 Reactive 파도에 올라탈 것인지, 어떤 파이썬 리엑티브 대표주자가 떠오를지 궁금하긴 합니다.


* Python reactive programming 책이 2017년 여름에 발매될 예정.

* 아래 링크는 파이썬 리엑티브 함수형 프로그래밍에 관한 글

https://jakubturek.com/functional-reactive-programming-in-python/



numpy

이것은 동시성 하고는 조금 다른 얘기지만 한마디 언급하겠습니다. Numpy 는 파이썬에서 데이터에 대한 계산(벡터,행렬등) 을 다룰 때 주로 사용합니다. 파이썬 list 등을 그대로 사용해서 계산할 수 도 있지만, Numpy 를 활용하면, 네이티브C 수준의 속도를 얻을 수 있습니다. 산술 계산에서 특히 중요한 역할을 하는 메모리의 지역성과 CPU가 지원하는 벡터화된 연산의 이점도 얻게 됩니다. 수십배 빨라진다고 보면 됩니다.



PYCUDA

글 서두에 병렬성에 대한 얘기를 하다가 GPGPU 를 통한 방식을 이용해 엄청난 계산을 빠르게 수행하는 예를 들어보았습니다. (쉐이더 볼륨랜더링) 네 파이썬도 CUDA를 사용해서 대규모 병렬 계산에 활용할 수 있습니다.

PyCUDA 홈페이지 바로가기



tensorflow (텐서플로우)

대세는 머신러닝 아니겠습니까? 대규모 병렬데이터 처리를 텐서플로우로 한다면 CUDA 를 통한 기본적인 병렬도 가능하며 텐서플로우 자체적으로 제공하는 다양한 머신러닝 API 도 익혀서 나중에 딥러닝이 필요할때 빠르게 적용 할 수 있는 장점이 있지 않을까요





먼가 굉장히 서두르면서 글이 끝마쳐지는 느낌이네요. 뭐 방대한 내용을 하나의 페이지에 모두 담긴 힘들다는 점..제 내공이 많이 부족하다는 점...이해해주시구요. 이상 글을 마칩니다.




레퍼런스:

- 고성능 파이썬
- 전문가를 위한 파이썬
- 프로그래머가 몰랐던 멀티코어 CPU 이야기
- CUDA volume rendering example 
- https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e
- http://www.getoffmalawn.com/blog/playing-with-asyncio
- https://snarky.ca/how-the-heck-does-async-await-work-in-python-3-5/
https://www.blog.pythonlibrary.org/2016/08/02/python-201-a-multiprocessing-tutorial/
http://sahandsaba.com/understanding-asyncio-node-js-python-3-4.html

연재 순서 

1. threading
2. Condition & Semaphore
3. Queue
4. multiprocessing
5. 비동기 (gevent) 
6. 분산 (celery)
7. GPGPU (PyCUDA)
8. 코루틴,asyncio,async/awiat
9. concurrent.future

4. Multiprocessing

멀티 프로세싱 모듈은 Python 버전 2.6에 추가되었습니다. 원래 버전은 Jesse Noller와 Richard Oudkerk에 의해  PEP 371에서 정의되었었습니다. multiprocessing 모듈을 사용하면 스레딩 모듈로 스레드를 생성 할 수있는 것과 동일한 방식으로 프로세스를 생성 할 수 있습니다. 여기서 주요 포인트는 프로세스를 생성하기 때문에 GIL (Global Interpreter Lock)을 피하고 시스템의 여러 프로세서를 최대한 활용할 수 있다는 것입니다.

multiprocessing  패키지에는 또한 스레딩 모듈에 없는 몇 가지 API가 포함되어 있습니다. 예를 들어 여러 입력에서 함수 실행을 병렬화하는 데 사용할 수있는 깔끔한 Pool 클래스가 있습니다. 우리는 이후 섹션에서 Pool을 볼 것입니다. 먼저 multiprocessing   모듈의 Process 클래스부터 시작하겠습니다.

multiprocessing 시작하기

Process 클래스는 스레딩 모듈의 Thread 클래스와 매우 유사합니다. 같은 함수를 호출하는 일련의 프로세스를 만들어 봅시다.

import os   from multiprocessing import Process   def doubler(number): result = number * 2 proc = os.getpid() print('{0} doubled to {1} by process id: {2}'.format( number, result, proc))   if __name__ == '__main__': numbers = [5, 10, 15, 20, 25] procs = []   for index, number in enumerate(numbers): proc = Process(target=doubler, args=(number,)) procs.append(proc) proc.start()   for proc in procs: proc.join()

이 예제에서는 Process를 이용해서 doubler 함수를 실행합니다. 함수 내에서 전달 된 숫자를 두 배로 늘립니다. 또한 파이썬의 os 모듈을 사용하여 현재 프로세스의 ID (또는 pid)를 가져옵니다. 이것은 어떤 프로세스가 함수를 호출하는지 알려줍니다. 그런 다음 아래쪽의 코드 블록에서 일련의 프로세스를 만들고 시작합니다. 마지막 마지막 루프는 각 프로세스에서 join () 메소드를 호출하기 만합니다.이 메소드는 파이썬에게 프로세스가 종료 될 때까지 대기하도록 지시합니다. 프로세스를 중지해야하는 경우 terminate () 메소드를 호출 할 수 있습니다.

결과) 
5 doubled to 10 by process id: 10468
10 doubled to 20 by process id: 10469
15 doubled to 30 by process id: 10470
20 doubled to 40 by process id: 10471
25 doubled to 50 by process id: 10472

때로는 프로세스에 사람이 읽을 수있는 이름을 사용하는 것이 더 좋을 수도 있습니다.

import os   from multiprocessing import Process, current_process     def doubler(number): result = number * 2 proc_name = current_process().name print('{0} doubled to {1} by: {2}'.format( number, result, proc_name))     if __name__ == '__main__': numbers = [5, 10, 15, 20, 25] procs = [] proc = Process(target=doubler, args=(5,))   for index, number in enumerate(numbers): proc = Process(target=doubler, args=(number,)) procs.append(proc) proc.start()   proc = Process(target=doubler, name='Test', args=(2,)) proc.start() procs.append(proc)   for proc in procs: proc.join()

이번에는 current_process라는 것을 추가로 가져옵니다. current_process는 기본적으로 스레딩 모듈의 current_thread와 동일합니다. 우리는이 함수를 사용하여 함수를 호출하는 스레드의 이름을 가져옵니다. 처음 5 개 프로세스에 대해서는 이름을 설정하지 않는다는 점에 유의하십시오. 그런 다음 여섯 번째 단계에서 프로세스 이름을 "Test"로 설정합니다. 결과는 다음과 같습니다.

5 doubled to 10 by: Process-2
10 doubled to 20 by: Process-3
15 doubled to 30 by: Process-4
20 doubled to 40 by: Process-5
25 doubled to 50 by: Process-6
2 doubled to 4 by: Test

출력은 multiprocessing 모듈이 기본적으로 이름의 일부로 각 프로세스에 번호를 지정한다는 것을 보여줍니다. 물론 이름을 지정할 때 번호가 추가되지는 않습니다.

multiprocessing.Queue vs multiprocessing.Manager().Queue

가 있는데 개인적으로 Manager().Queue를 모든 경우에서 디폴트로 사용하라고 권하고 싶다. 

Demon

일반적으로 데몬쓰레드라고 하면, 메인이 죽으면 같이 죽는 쓰레드를 말한다.

멀티쓰레드 

데몬 쓰레드란 백그라운드에서 실행되는 쓰레드로 메인 쓰레드가 종료되면 즉시 종료되는 쓰레드이다. 
디폴트는 넌데몬이며, 
해당 서브쓰레드는 메인 쓰레드가 종료할 지라도 자신의 작업이 끝날 때까지 계속 실행된다.

멀티프로세스 

 프로세스가 종료되면 그것의 자식 데몬 프로세스들을 강제종료 시킬 것이다. 데몬 프로세스는 자식 프로세스를 생성 할 수 없으며, 유닉스 데몬이나 서비스가 아니다. 디폴트인 넌데몬프로세스 (daemon = False) 일 경우에는 해당 프로세스가 종료 될 때까지 메인프로세스는 종료되지 않는다. 암시적으로 내부에서 join() 하고 있다.  
참고로 메인프로세스가 갑자기 죽은 경우 데몬 자식 프로세스를 종료하지 못한다.

Locks

멀티 프로세싱 모듈은 스레딩 모듈과 거의 같은 방식으로 잠금을 지원합니다. 가져 오기 잠금, 가져 오기, 무언가를 수행하고 해제 만하면됩니다. 보시죠.

from multiprocessing import Process, Lock     def printer(item, lock): lock.acquire() try: print(item) finally: lock.release()   if __name__ == '__main__': lock = Lock() items = ['tango', 'foxtrot', 10] for item in items: p = Process(target=printer, args=(item, lock)) p.start()

여기에서는 전달한 내용을 print 하는 간단한 print 기능을 만듭니다. 프로세스가 서로 간섭하지 않도록 Lock 객체를 사용합니다. 이 코드는 세 항목의 목록을 반복하고 각 항목에 대한 프로세스를 만듭니다. 각 프로세스는 함수를 호출하고 iterable의 항목 중 하나를 전달합니다. 우리가 잠금을 사용하고 있기 때문에, 다음 라인의 프로세스는 계속하기 전에 잠금이 해제 될 때까지 기다릴 것입니다.

Logging

로깅 프로세스는 로깅 스레드와 약간 다릅니다. 그 이유는 파이썬의 로깅 패키지가 프로세스 공유 잠금을 사용하지 않기 때문에 서로 다른 프로세스의 메시지가 섞여서 끝날 수 있기 때문입니다. 이전 예제에 기본 로깅을 추가해 보겠습니다. 코드는 다음과 같습니다.

import logging
import multiprocessing
 
from multiprocessing import Process, Lock
 
def printer(item, lock):
    """
    Prints out the item that was passed in
    """
    lock.acquire()
    try:
        print(item)
    finally:
        lock.release()
 
if __name__ == '__main__':
    lock = Lock()
    items = ['tango', 'foxtrot', 10]
    multiprocessing.log_to_stderr()
    logger = multiprocessing.get_logger()
    logger.setLevel(logging.INFO)
    for item in items:
        p = Process(target=printer, args=(item, lock))
        p.start()

로그하는 가장 간단한 방법은 모두 stderr로 보내는 것입니다. 우리는 log_to_stderr () 함수를 호출하여 이를 수행 할 수 있습니다. 그런 다음 get_logger 함수를 호출하여 로거에 액세스하고 로깅 수준을 INFO로 설정합니다. 나머지 코드는 동일합니다. 여기서 join () 메서드를 호출하지 않는다는 것에 주목 하십시요. 대신 부모 스레드 (즉, 스크립트)가 종료 될 때 암시적으로 join ()을 호출합니다.

결과는 아래와 같습니다.

[INFO/Process-1] child process calling self.run()
tango
[INFO/Process-1] process shutting down
[INFO/Process-1] process exiting with exitcode 0
[INFO/Process-2] child process calling self.run()
[INFO/MainProcess] process shutting down
foxtrot
[INFO/Process-2] process shutting down
[INFO/Process-3] child process calling self.run()
[INFO/Process-2] process exiting with exitcode 0
10
[INFO/MainProcess] calling join() for process Process-3
[INFO/Process-3] process shutting down
[INFO/Process-3] process exiting with exitcode 0
[INFO/MainProcess] calling join() for process Process-2

이제 로그를 디스크에 저장하려면 좀 더 까다로워집니다. 파이썬의 logging Cookbook에서 그 주제에 관해 읽을 수 있습니다.

The Pool Class

Pool 클래스는 작업자 프로세스 풀을 나타내는 데 사용됩니다. 여기에는 작업을 작업 프로세스로 offload  할 수있는 방법이 있습니다. 정말 간단한 예를 살펴 보겠습니다.

from multiprocessing import Pool
 
def doubler(number):
    return number * 2
 
if __name__ == '__main__':
    numbers = [5, 10, 20]
    pool = Pool(processes=3)
    print(pool.map(doubler, numbers))

기본적으로 여기서 발생하는 것은 Pool의 인스턴스를 만들고 세 개의 작업자 프로세스를 생성하도록 지시한다는 것입니다. 그런 다음 map 메소드를 사용하여 함수와 반복 가능한 것을 각 프로세스에 매핑합니다. 마지막으로 결과를 인쇄합니다.이 경우 실제로 목록입니다 : [10, 20, 40].

또한 apply_async 메소드를 사용하여 풀에서 프로세스의 결과를 얻을 수 있습니다.

from multiprocessing import Pool
 
def doubler(number):
    return number * 2
 
if __name__ == '__main__':
    pool = Pool(processes=3)
    result = pool.apply_async(doubler, (25,))
    print(result.get(timeout=1))

우리가 할 수있는 것은 process 의 결과를 요구하는 것입니다. get 함수가 그것을 하죠. 우리가 호출 한 기능에 문제가 생길 경우를 대비해서 타임 아웃이 설정되었음을 알 수 있습니다. 우리는 무기한 차단되면 문제가 생길 수도 있으니까요.

Process Communication

communicating 모듈은 프로세스 간 통신시 Queues 와 Pipes라는 두 가지 기본 방법을 사용합니다. 큐는 실제로 스레드와 프로세스에서 빈번히 사용되며 잘 구현되어 있습니다. 큐에 대해 매우 간단한 예제를 살펴 보겠습니다.

* 주의 사항 : multiprocessing 의 Queue 입니다. 그냥 import Queue 는 쓰레드간에 사용됨. 

from multiprocessing import Process, Queue   sentinel = -1   def creator(data, q): """ Creates data to be consumed and waits for the consumer to finish processing """ print('Creating data and putting it on the queue') for item in data:   q.put(item)     def my_consumer(q): while True: data = q.get() print('data found to be processed: {}'.format(data)) processed = data * 2 print(processed)   if data is sentinel: break     if __name__ == '__main__': q = Queue() data = [5, 10, 13, -1] process_one = Process(target=creator, args=(data, q)) process_two = Process(target=my_consumer, args=(q,)) process_one.start() process_two.start()   q.close() q.join_thread()   process_one.join() process_two.join()

먼저 Queue 및 Process를 import 합니다. 그런 다음 데이터를 생성하여 큐에 추가하고 데이터를 소비하고 처리하는 두 가지 기능을 수행합니다. Queue에 데이터를 추가하는 것은 Queue의 put () 메소드를 사용하는 반면 Queue에서 데이터를 가져 오는 것은 get 메소드를 통해 수행됩니다. 코드의 마지막 덩어리는 Queue 객체와 두 개의 프로세스를 생성 한 다음 실행합니다. Queue 자체보다는 프로세스 객체에 대해 join ()을 호출한다는 것을 알 수 있습니다.

*  쓰레드를 위한 Queue 는 따로 있음을 명심하십시요. 그 큐는 Queue.Queue(10) 이렇게 사용합니다.

LifoQueue 

참고로 멀티프로세서용 Queue 는 FIFO 인데, LIFO 로 하려면 ( 블로그 독자님이 메일로 물어보셔서 찾아봄) 

multiprocessing-managers 라는 것을 통해서 해결 할 수 있을거 같다.
아래는 LifoQueue 예제입니다. LifQueue 자체는 멀티프로세서에서 사용못하지만 
managers 를 통해서 사용 할 수 있게 하는거 같습니다. 테스트를 통해 확인 하고 사용하세요.

from multiprocessing import Process
from multiprocessing.managers import BaseManager
from time import sleep
from queue import LifoQueue


def run(lifo):
    """Wait for three messages and print them out"""
    num_msgs = 0
    while num_msgs < 3:
        # get next message or wait until one is available
        s = lifo.get()
        print(s)
        num_msgs += 1


# create manager that knows how to create and manage LifoQueues
class MyManager(BaseManager):
    pass
MyManager.register('LifoQueue', LifoQueue)


if __name__ == "__main__":

    manager = MyManager()
    manager.start()
    lifo = manager.LifoQueue()
    lifo.put("first")
    lifo.put("second")

    # expected order is "second", "first", "third"
    p = Process(target=run, args=[lifo])
    p.start()

    # wait for lifoqueue to be emptied
    sleep(0.25)
    lifo.put("third")

    p.join()


레퍼런스:

https://www.blog.pythonlibrary.org/2016/08/02/python-201-a-multiprocessing-tutorial/

https://pymotw.com/2/multiprocessing/basics.html

https://docs.python.org/2/library/multiprocessing.html#multiprocessing.Process.daemon

연재 순서 

1. threading
2. Condition & Semaphore
3. Queue
4. multiprocessing
5. 비동기 (gevent) 
6. 분산 (celery)
7. GPGPU (PyCUDA)
8. 코루틴,asyncio,async/awiat
9. concurrent.future


2. Condition

파이썬의 Condidtion 은 쉽게 생각하면 Event + Mutex 쯤으로 보면된다.
다음예를보면 소비자 쓰레드들은 Condition 이 set 이 되길 기다리고 있다. 생산자 쓰레드는 이 Condition을 set 해줘서 다른 쓰레드들에게 진행해도 좋다고 고지한다. 기다리고 있던 쓰레드 모두가 통과할 수 는 없고 상호 배제되어 하나씩 통과된다 . 

import threading import time import logging logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-9s) %(message)s',) def consumer(cv): logging.debug('Consumer thread started ...') with cv: logging.debug('Consumer waiting ...') cv.wait() time.sleep(3) logging.debug('Consumer consumed the resource') def producer(cv): logging.debug('Producer thread started ...') with cv: logging.debug('Making resource available') logging.debug('Notifying to all consumers') cv.notifyAll() if __name__ == '__main__': condition = threading.Condition() cs1 = threading.Thread(name='consumer1', target=consumer, args=(condition,)) cs2 = threading.Thread(name='consumer2', target=consumer, args=(condition,)) pd = threading.Thread(name='producer', target=producer, args=(condition,)) cs1.start() time.sleep(1) cs2.start() time.sleep(2) pd.start()


(consumer1) Consumer thread started ...

(consumer1) Consumer waiting ...

(consumer2) Consumer thread started ...

(consumer2) Consumer waiting ...

(producer ) Producer thread started ...

(producer ) Making resource available

(producer ) Notifying to all consumers

(consumer2) Consumer consumed the resource

(consumer1) Consumer consumed the resource


threading.Condition vs threading.Event

질문:

지금까지 Condition과 Event 에 대한 명확한 차이점에 대한 설명을 찾지 못해서 드리는 질문입니다. 각각의 사용처에 대해 알려 주실 수 있나요? 제가 지금까지 찾은 예제들은 모두 생산자-소비자모델이었는데요. queue.Queue가 더 좋은솔루션인듯요.


답변:

간단하게 말해서 쓰레드들이 다른곳에서 무엇인가 true 가 될 때까지 기다려야한다면 그리고 일단 그것이 true가 된다면 공유된 자원에 엑세스하는것이 배제적이 될때 Condition이 일반적이고, 반면에 Event 는 쓰레드들이 단지 무엇이 true가 되기만을 기다리는것에 흥미를 가지고 있으면 사용된다.


2-2 Semaphore

파이썬의 Semaphore 는 정해진 갯수의 쓰레드만 통과시켜준다. 예를들어 웹크롤링을 하는 쓰레드를 50개정도로 한정지어 놓는데 사용할 수 있다.  아래 예제에서는 10개의 쓰레드 중에서 3개만 일을 하도록 한다. 만약 세마포어를 통과한 3개의 쓰레드가 동일한 리소스를 사용하려고 할 때는 그들끼리 Lock 을 통해서 상호배제되어야 할 것이다.

import threading import time import logging logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-9s) %(message)s',) class ThreadPool(object): def __init__(self): super(ThreadPool, self).__init__() self.active = [] self.lock = threading.Lock() def makeActive(self, name): with self.lock: self.active.append(name)

time.sleep(5) logging.debug('Running: %s', self.active) def makeInactive(self, name): with self.lock: self.active.remove(name) logging.debug('Running: %s', self.active) def f(s, pool): logging.debug('Waiting to join the pool') with s: name = threading.currentThread().getName() pool.makeActive(name) time.sleep(1) pool.makeInactive(name) if __name__ == '__main__': pool = ThreadPool() s = threading.Semaphore(3) for i in range(10): t = threading.Thread(target=f, name='thread_'+str(i), args=(s, pool)) t.start()



2-3 Event

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-9s) %(message)s',)
                    
def wait_for_event(e):
    logging.debug('wait_for_event starting')
    event_is_set = e.wait()
    logging.debug('event set: %s', event_is_set)

def wait_for_event_timeout(e, t):
    while not e.isSet():
        logging.debug('wait_for_event_timeout starting')
        event_is_set = e.wait(t)
        logging.debug('event set: %s', event_is_set)
        if event_is_set:
            logging.debug('processing event')
        else:
            logging.debug('doing other things')

if __name__ == '__main__':
    e = threading.Event()
    t1 = threading.Thread(name='blocking', 
                      target=wait_for_event,
                      args=(e,))
    t1.start()

    t2 = threading.Thread(name='non-blocking', 
                      target=wait_for_event_timeout, 
                      args=(e, 2))
    t2.start()

    logging.debug('Waiting before calling Event.set()')
    time.sleep(3)
    e.set()
    logging.debug('Event is set')



레퍼런스:

http://www.bogotobogo.com/python/Multithread/python_multithreading_Synchronization_Condition_Objects_Producer_Consumer.php

연재 순서 

1. threading
2. Condition & Semaphore
3. Queue
4. multiprocessing
5. 비동기 (gevent) 
6. 분산 (celery)
7. GPGPU (PyCUDA)
8. 코루틴,asyncio,async/awiat
9. concurrent.future

1. 쓰레드 

파이썬에서의 쓰레드는 보통 Thread 혹은 Threading 모듈을 사용할 수 있다. 
또한 Queue 모듈을 통해서 생산자-소비자 패턴을 구현한다. 여러가지 이유로 Thread 보다는 Threading 모듈을 사용하길 추천한다.따라서 이 글에서는 Threading 과 Queue 에 대해서 알아본다.

daemon 

파이썬에서 thread 는 기본적으로 daemon 속성이 False 인데, 메인 쓰레드가 종료되도 자신의 작업이 끝날 때까지 계속 실행된다. 부모가 종료되면 즉시 끝나게 하려면 True 를 해줘야한다. 데몬속성은 반드시 start 이전에 호출되어야 한다. 

Threading 

Threading 모듈은 아래와 같은 객체들을 가지고 있다.

 객체

 설명 

Thread 

단일 실행 쓰레드를 만드는 객체 

Lock 

기본적인 락 객체 

RLock 

재진입 가능한 락객체. 이미 획득한 락을 다시 획득 할 수 있다. 

Condition 

다른 쓰레드에서 신호를 줄 때까지 기다릴 수 있는 컨디션 변수 객체  

Event 

컨디션 변수의 일반화 버전. 

Semaphore 

정해놓은 갯수만큼의 쓰레드를 허용하는 동기화 객체. (예를들어 최대 50개만 동시에 실행) 

BoundedSemaphore 

초기 설정된 값 이상으로 증가 될 수 없게 재한한 Semaphore 

Timer 

Thread 와 비슷하지마 실행되기 전에 지정된 시간 동안 대기  

Barrier 

쓰레드들이 계속 진행 할 수 있으려면 지정된 숫자의 쓰레드가 해당 지점까지 도달해야하게 만듬 (파이썬 3.2에서 처음 소개됨) 


Thread 클래스

 메소드 / 속성

 설명 

 daemon

 데몬쓰레드인지 - 기본은 False, 즉 부모쓰레드가 종료되도 살아있다. 

  __init__(group,target,name,args,kwargs={},verbose,daemon) 

 객체를 초기화한다

 start()

 쓰레드를 실행한다.

 run()

 쓰레드의 기능을 정희하는 메소드 (상속해서 오버라이드됨)  

 jon(timeout=None)

 쓰레드가 종료될때까지 대기한다.


Thread 생성방식

1. 함수를 전달해서

import threading
from time import sleep, ctime

loops = [8,2]

def loop(nloop,nsec):
print 'start loop', nloop, 'at:',ctime()
sleep(nsec)
print 'loop', nloop, 'at:', ctime()


def test() :
print 'starting at:', ctime()
threads = []
nloops = range(len(loops))

for i in nloops:
t = threading.Thread(target=loop,args=(i, loops[i]))
threads.append(t)

for i in nloops:
threads[i].start()

for i in nloops:
threads[i].join()

print 'all Done at: ', ctime()

if __name__ == '__main__'

test()

하나는 8초, 하나는 2초동안 실행되는 2개의 쓰레드를  threading.Thread 에 함수를 지정해서 실행한다. 

2. 상속을 통해 

import threading
from time import sleep, ctime

loops = [8,2]

class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self,name=name)
self.func = func
self.args = args

def run (self):
self.func(*self.args) # 함수를 받아서 처리하는게 아니라 여기에 직접 구현하는 경우가 일반적..

def loop(nloop,nsec):
print 'start loop', nloop, 'at:',ctime()
sleep(nsec)
print 'loop', nloop, 'at:', ctime()


def test() :
print 'starting at:', ctime()
threads = []
nloops = range(len(loops))

for i in nloops:
t = MyThread(loop, (i,loops[i]),loop.__name__)
threads.append(t)

for i in nloops:
threads[i].start()

for i in nloops:
threads[i].join()

print 'all Done at: ', ctime()

if __name__ == '__main__'

 test()

threading.Thread 를 상속받은 클래스에 run 함수를 오버라이드하여 사용한다.

threading.Thread.__init__ 를 반드시 해야한다.


레퍼런스:

코어 파이썬 어플리케이션 프로그래밍

http://stackoverflow.com/questions/7424590/threading-condition-vs-threading-event

http://www.bogotobogo.com/python/Multithread/python_multithreading_Synchronization_Condition_Objects_Producer_Consumer.php

+ Recent posts