[ Python ] thread 공부해보기-1

2019. 8. 11. 14:38분석 Python/구현 및 자료

728x90

도움이 되셨다면, 광고 한번만 눌러주세요.  블로그 관리에 큰 힘이 됩니다 ^^

최근에 머 하나를 병렬적으로 실행하고 싶은 게 있었다.

그래서 멀티프로세싱으로 하려고 시도를 했는데, 동작하지 않아서 threading을 활용해서 thread 형식으로 했다.

하지만 내가 알기론 GIL? 이것 때문에 thread 방식은 안 좋다고 하는데, 왜 내가 했던 문제에서는 thread로만 작동하는 걸까?! 

 

암튼 그래서 thread에 대해서 공부를 해보려고 한다.

일단 동기와 비동기에 대해 간다하게 설명된 예시

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

출처: https://hamait.tistory.com/833 [HAMA 블로그]


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

네 이것은 싱글쓰레드 / 동기 처리입니다. 동기? 왜 동기 일까요? 
사실 이것을 비동기로 봐도 되긴 합니다.
왜냐면 손님은 일단 기다리지 않고 (블럭되지 않고) 자기 할일을 할 수 있기 때문에, 비동기로 봐도 됩니다.

파이썬에서의 동시성 

멀티 쓰레드

파이썬에선 GIL이 존재함 ( 한 순간에 하나의 스레드만 작동하도록 만드는 것 ) 즉 CPU가 여러 개 있어도 하나만 사용하게 함.  스레드를 해당 순간에 하나만 사용해서 락이 필요 없어 보이지만, 문제가 생길 수 있어서 Lock Event 같은 객체가 있음. 

자 기억합시다!! 파이썬에서 스레드 활용은 I/O , 비동기에서만 활용하자,

병렬 계산을 위해서라면 프로세스, GPU, 분산을 활용하자!! 

 

 

 

일단 프로세스는 하나의 루틴을 가지고 있다고 한다. 즉 직렬적(Serial)하게 한 개의 일을 순서대로 처리한다.

 

스레드를 사용하면, 하나의 프로세스에서 여러 개의 루틴을 만들어서 병렬적으로 실행할 수 있게 한다.

1. CPU 사용률 향상

2. 효율적인 자원 활용 및 응답성 향성

3. 코드 간결 및 유지보수성 향상

 

 

하나의 프로세스는 여러개의 Thread를 가질 수 있다.

 

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

 

파이썬 동시성 프로그래밍 - (1) 쓰레드

연재 순서 1. threading 2. Condition & Semaphore 3. Queue 4. multiprocessing 5. 비동기 (gevent) 6. 분산 (celery) 7. GPGPU (PyCUDA) 8. 코루틴,asyncio,async/awiat 9. concurrent.future 1. 쓰레드 파이썬..

hamait.tistory.com

threading 함수에 객체

 

thread라는 모듈도 스레드를 지원하지만, low level 라이브러리로 사용이 복잡하고 어렵다.

그래서 여기서는 high level의 threading 함수를 쓰려고 한다. 

 

def execute(number) :
    """
    쓰레드가 실행할 함수
    """
    return print(threading.currentThread().getName() , number )

if __name__ == "__main__" :
    for i in range(1, 10 ) :
        my_thread = threading.Thread( target=  execute , args = (i, ))
        my_thread.start()

보통 저런 경우 excute 함수는 보통 웹이나 TCP 소켓 등을 통해 remote에 접속해서 무엇인가 가져온다는지 하는 작업(I/O)에 주로 쓰인다고 함. 

그래서 excute에서 생성 혹은 가져온 데이터는 메인 스레드에서 데이터를 전달해줌

그때 queue를 활용! 

queue는 내부적으로 베타 제어를 하고 있기 때문에 스레드에 안전합니다.

 

 

 

 

def get_logger():
    logger = logging.getLogger("Thread Ex")
    logger.setLevel(logging.DEBUG)
    fh = logging.StreamHandler()
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    return logger

def execute(number, logger):
    """
    쓰레드에서 실행 할 함수(로깅 사용)
    """
    logger.debug('execute function executing')
    result = number * 2
    logger.debug('execute function ended with: {}'.format(result))

if __name__ == '__main__':
    #logger 생성 
    logger = get_logger()
    for i, name in enumerate(['Kim', 'Lee', 'Park', 'Cho', 'Hong']):
        my_thread = threading.Thread(
            target=execute, name=name, args=(i,logger))
        my_thread.start()

 

 

Thread - 동기화

Thread는 보통 둘 이상의 실행 흐름을 가지고 있기 때문

공통 메모리 영역의 값을 참조하는 과정에서 동일한 데이터를 조작하는 등의 일련의 과정이 일어날 수 있음

 

문제 발생 가능성 ↑

Solution

1. Thread의 실행 순서 조정 및 메모리 접근 제한 등으로 문제를 해결

-> 동기화의 기법 필요함

 

코드가 동기화 구현이 안되어서 좋지 않은 예!

import threading

total = 0

def add_total(amount):
    """
    쓰레드에서 실행 할 함수
    전역변수 total에 amount 더하기
    """
    global total
    total += amount
    print (threading.currentThread().getName()+' Not Synchronized  :',tot)

#동기화가 되어 있지 않은 쓰레드 예제
if __name__ == '__main__':
    for i in range(10000):
        my_thread = threading.Thread(
            target=add_total, args=(1,))
        my_thread.start()

코드가 동기화 구현이 되어서 좋은 예!

lock을 통해서 먼저 진압한 스레드가 작업을 완료한 후

다른 스레드에게 제어권을 넘기는 과정을 통해서 스레드의 동기화를 가능하게 함!

import threading

tot = 0
lock = threading.Lock()

def add_total(amount):
    """
    쓰레드에서 실행 할 함수
    전역변수 tot에 amount 더하기
    """
    global tot
    lock.acquire()
    try:
        tot += amount
    finally:
        lock.release()
    print (threading.currentThread().getName()+' Synchronized  :',tot)

    """
    또는

    global total
    with lock:
        total += amount
    print (threading.currentThread().getName()+' Synchronized  :',tot)

    with 문으로 더 간단하게 사용 가능
    """

#동기화가 되어 있는 쓰레드 예제
if __name__ == '__main__':
    for i in range(10000):
        my_thread = threading.Thread(
            target=add_total, args=(1,))
        my_thread.start()

코드 일부 수정

사실 크게 먼가 와 닿지가 않는다 그래서 위의 코드를 좀 바꿔서 다시 해봤다.

lock = threading.Lock()
def get_logger():
    logger = logging.getLogger("Thread Ex")
    logger.setLevel(logging.DEBUG)
    fh = logging.StreamHandler()
    fmt = '%(asctime)s - %(threadName)s - %(levelname)s - %(message)s'
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    return logger

def execute(number, logger):
    """
    쓰레드에서 실행 할 함수(로깅 사용)
    """
    lock.acquire()
    try:
        logger.debug('execute function executing')
        result = number * 2
        logger.debug('execute function ended with: {}'.format(result))
    finally:
        lock.release()
    
    
if __name__ == '__main__':
    #logger 생성 
    logger = get_logger()
    for i, name in enumerate(['Kim', 'Lee', 'Park', 'Cho', 'Hong']):
        my_thread = threading.Thread(
            target=execute, name=name, args=(i,logger))
        my_thread.start()

그래서 여기서 보면 위에서 말한 것과 같이 lock을 사용하니 한 개가 끝나면 다음 것이 시행되는 것 같았다!

 

왼쪽 lock 하기 전 / 오른쪽 lock 한 것

 

 

https://niceman.tistory.com/138

 

파이썬(Python) - Thread(쓰레드) 설명 및 예제 소스 코드(1) - 기초

파이썬(Python) Thread - 설명 프로그래밍 언어를 떠나서 개발자에게 있어서 쓰레드를 능숙하게 활용할 수 있는 스킬은 정말 중요한 부분이라고 할 수 있습니다. 프로세스의 흐름 및 기타 연관된 동작 관계에 대해..

niceman.tistory.com

https://hamait.tistory.com/833

 

파이썬과 동시성에 대한 정리

파이썬과 동시성 *본 글은 대략 동시성 프로그래밍에 대해서 알고 있는데 파이썬은 시작 단계이며 어떤것들이 있는지 빠르게 훑어보고 싶은 분들을 위해 눈높이가 맞추어져 있음을 알려드립니다. 기본 쓰레드에서..

hamait.tistory.com

 

728x90