import threading
import time

class MyThread(threading.Thread):
    def __init__(self, msg):
        threading.Thread.__init__(self)
        self.g_msg = msg
        self.g_daemon = True
    # End of __init__

    def run(self):
        while True:
            time.sleep(1)
            print self.g_msg

# End of MyThread

for v_msg in ['I', 'love', 'you']:
    v_thread = MyThread(v_msg)
    v_thread.start()
# for

for i in range(100):
    time.sleep(0.1)
    print i
# for

Python에서 쓰레드를 가능하게 해주는 모듈은 "threading.Thread"
  1. MyThread class는 threading.Thread를 상속받고 MyThread(threading.Thread)
  2. 쓰레드를 클래스로 정의할 경우에는 __init__ 함수에서 부모 클래스의 생성자 threading.Thread.__init__(self)를 반드시 호출 !!
  3. MyThread의 instance의 start 함수를 실행하면 MyThread 클래스의 run 함수가 자동 실행

클래스를 사용하지 않고 함수 내에서 thread를 사용하고 싶을 경우에는 어떻게 해야 할까요?

import threading

def MyThread():

    def consumer(v_value):

        print "{} => {}".format(threading.currentThread().getName(), v_value)

    # End of consumer

    for i in range(10):
        threading.Thread(target=consumer, args=(i, )).start()

# End of MyThread

간단합니다.
함수 내의 함수 (nested function)를 정의 후 이 또한 threading.Thread로 호출해주면 됩니다.
클래스로 구현한 경우에는 클래스 내에서 __init__함수와 run 함수를 구현해줘야 하지만,
함수 내에서는 target과, args를 정의하고 start를 해주면 됩니다.

위의 경우에는 잘 다룬다면야 상관없지만, 값의 범위를 미리 알아야 하고 그 개수만큼 쓰레드를 생성해야 합니다.
다시 말해서, 2~3개의 쓰레드 개수로도 충분히 해결할 수 있는데, 쓸데없이 더 많은 쓰레드를 생성해야 하는 상황입니다.

그럼, 2~3개의 쓰레드만 생성하고 10 혹은 100개의 값을 처리하고자 할 때는 어떻게 할까요?
일반적으로, 프로그램을 설계할 때 Queue를 많이 사용합니다.
비단, Python으로만 국한되지 않고, C/C++, Java와 같이 어떤 언어에서라도 Queue를 많이 도입합니다.


import sys
import threading
import Queue

def MyThread():

    v_queue = Queue.Queue()

    def consumer():
        while True:
            try:
                v_value = v_queue.get(False, 10)
                print "{} => {}".format(threading.currentThread().getName(), v_value)
                v_queue.task_done()
            except Exception:
                pass

            if v_queue.empty():
                break

    # End of consumer

    for i in range(1000):
        v_queue.put(i)

    for _ in range(5):
        threading.Thread(target=consumer).start()

    v_queue.join()

# End of MyThread

이러 방식으로 Queue를 발행 후, produce(put 함수)와 consume(get함수)을 한다면 값의 개수에 따라 쓰레드를 생성하지 않아도 시스템을 효율적으로 사용할 수도 있겠습니다.


마지막으로, 1번째 클래스를 이용한 방법과 3번째 Queue를 이용하는 방법을 믹스합니다.
위의 방법을 사용하는 건데, 프로그램을 작성하다 보면 깔끔하게 작성해야 합니다.
1번처럼 작성하다 보면 하나의 파일에 너무 많은 코드가 들어가면서 복잡해지겠죠?

최종적으로 다음과 같이 합니다.
  1. Producer와 Consumer가 같은 Queue를 바라봐야 합니다. -> Queue는 전역처럼 관리되어야겠죠?
  2. 당연히 쓰레딩이 되도록 합니다.

import sys
import threading
import Queue

class Consumer(threading.Thread):

    def __init__(self, v_queue):

        # for thread
        threading.Thread.__init__(self)
        self.setDaemon(True)

        # for queue
        self.g_queue = v_queue

    # End of __init__

    def run(self):

        while True:
            try:
                v_value = self.g_queue.get()   # if queue is empty, queue is blocked
                print "{} => {}".format(threading.currentThread().getName(), v_value)
                self.g_queue.task_done()
            except Exception:
                pass

            if self.g_queue.empty():
                break
        # while

    # End of run

# End of Consumer


def MyThread():

        v_queue = Queue.Queue()

        for i in range(5):
                v_consumer = Consumer(v_queue)
                v_consumer.start()

        for i in range(1000):
                v_queue.put(i)

        v_queue.join()

# End of MyThread


Queue를 전역 변수로 생성 후 사용해도 되겠지만, 실전에서는 그렇게 하면 안 되겠죠?
함수에서 Queue를 생성 후 Consumer thread에 인자로 전달했습니다.

참고로,
이번에는 MyThread 함수에서 Consumer thread가 먼저 호출되고, Producer가 작업토록 했습니다.
왜일까요?

그렇지 않고, Producer 작업 후 Consumer해도 되지만, 시스템 상으로 좋아 보이지가 않습니다.
Producing을 다 한 후 Consuming을 한다는 의미는 값들을 메모리 상에 전부 올려놓고 처리하겠다는 말과 같겠지요.

그래서, Consumer가 Queue에 값들이 들어오는지 안 오는지 모니터링하고 있다가  Producer가 값들을 넣자 말자 Consuming해서 처리한다면 시스템을 좀 더 효율적으로 사용한다고 봐야겠습니다.

Python에서 Threading 어렵지 않죠??


블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,