Thread, Process

21223 단어 pythonpython

Thread

Thread(쓰레드)는 프로그램의 실행흐름 입니다. 하나의 프로세스 안에서 여러개의 쓰레드를 만들 수 있습니다.
즉 프로세스가 부여된 자원을 이용해서 같은 프로세스 내에서 여러쓰레드들 끼리 자원을 공유할 수 있습니다.

쓰레드는 동시성을 위해서 만들어진 개념입니다.
하나의 프로세스 안에서 두개이상의 쓰레드를 만들게 되면 두개이상의 쓰레드가 동시에 일을 하게 됩니다.

import time

if __name__ == "__main__":
    
    increased_num = 0

    start_time = time.time()
    for i in range(100000000):
        increased_num += 1

    print("--- %s seconds ---" % (time.time() - start_time))

    print(f"increased_num= {increased_num}")
    print("end of main")

단일 프로세스의 단일 쓰레드로 일억번 +1을 하는 프로그램입니다.

결과


파이선에서 멀티 쓰레드를 사용하려면 threading 모듈을 임포트해서 사용해야합니다.
threading 모듈의 Thread 클래스에 target 인자에 쓰레드 함수를 지정해주고
args 로 매개변수를 전달할 수 있습니다.

그럼 하나의 쓰레드에서 천만까지 증가시키는 코드를 두개의 쓰레드로 분리해서 실행해보겠습니다.

import threading
import time

shared_number = 0

def thread_1(number):
    global shared_number
    print(f"number = {number}")
    
    for i in range(number):
        shared_number += 1

def thread_2(number):
    global shared_number
    print(f"number = {number}")
    for i in range(number):
        shared_number += 1

if __name__ == "__main__":

    threads = [ ]

    start_time = time.time()
    t1 = threading.Thread( target= thread_1, args=(50000000,) )
    t1.start()
    threads.append(t1)

    t2 = threading.Thread( target= thread_2, args=(50000000,) )
    t2.start()
    threads.append(t2)

    for t in threads:
        t.join()

    print("--- %s seconds ---" % (time.time() - start_time))

    print(f"shared_number= {shared_number}")
    print("end of main")

속도는 반으로 줄지도 않았고, 5000000씩 각각 쓰레드에 전달이 되었지만,
최종 증가된 숫자는 천만이 안되었습니다.

문제의 발생 원인 : 같은 변수를 동시에 접근 했기 때문

ex) a = a+1이 있을때
a = a+1은 보통 3가지의 명령어로 진행됩니다. 그리고 cpu는 명령어를 하나씩 실행합니다.
1. a의 값을 메모리에서 레지스터로 불러옵니다.
2. 레지스터에서 더합니다.
3. 더한 값을 실제로 a가 있는 메모리에 저장합니다.

a의 진짜 메모리에 1이 더해지려면 3번째 명령어까지 실행 되어야 합니다.
하지만 이 3가지 명령어를 4개의 thread가 거의 동시에 실행하려다 보니깐 한 thread에서 3번째 명령어가
다 끝나기도 전에 다른 thread에서 덧셈을 시작하게 됩니다.

shared_number가 천만으로 증가되지 않는 이유는 같은 전역 변수를 공유하기 때문에 서로 다른 쓰레드가 같은 value를 연산하기 때문입니다.


해결방법 : thread를 동기화 합니다.

동기화 하는 방법 중에 lock을 이용합니다.

Lock : 특정 thread에서 변수를 사용하기 때문에 다른 thread가 사용하지 못하도록 막는 역할입니다.

  • 마치 변수를 잠구는 것 같아 Lock이라고 부릅니다.
  • 변수를 다 사용했으면 thread변수에 대한 Lock을 풀어줘야하는데 이를 Release()로 사용합니다.
import threading

class ThreadVariable():
    def __init__(self):
        self.lock = threading.Lock()
        self.lockedValue = 0
 
    # 한 Thread만 접근할 수 있도록 설정한다
    def plus(self, value):
        # Lock해서 다른 Thread는 기다리게 만든다.
        self.lock.acquire()
        try:
            self.lockedValue += value
        finally:
            # Lock을 해제해서 다른 Thread도 사용할 수 있도록 만든다.
            self.lock.release()
 
# CounterThread
class CounterThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self, name='Timer Thread')
 
    # CounterThread가 실행하는 함수
    def run(self):
        global totalCount
 
        # 50,000,000번 카운트 시작
        for _ in range(5000000):
            totalCount.plus(1)
        print('50,000,000번 카운팅 끝!')
 
if __name__ == '__main__':
    # 전역 변수로 totalCount를 선언
    global totalCount
    # totalCount를 ThreadVariable 오브젝트로 초기화한다
    totalCount = ThreadVariable()

    for _ in range(2):
        timerThread = CounterThread()
        timerThread.start()
 
    mainThread = threading.currentThread()
    for thread in threading.enumerate():
        # Main Thread를 제외한 모든 Thread들이 
        # 카운팅을 완료하고 끝날 때 까지 기다린다.
        if thread is not mainThread:
            thread.join()
 
    print('totalCount = ' + str(totalCount.lockedValue))


Process

두개의 프로세스를 만들어 진정한 병렬 프로그래밍 방식으로 구현해 보겠습니다.
프로세스를 만들면 프로세스 별로 별도의 메모리 영역을 가지며 큐, 파이프 파일등을 이용한 프로세스 간 통신 과 같은 방법으로 통신을 구현 할 수 있습니다.
그리고 멀티 프로세스 프로그램은 각각 별도의 메모리 영역을 가지고 여러 작업을 동시에 나눠서 처리할 수 있습니다. 즉 작업을 동시에 나눠서 병렬로 작업을하면 하나의 프로세스에서 작업하던 것을 두배이상 빠르게 할 수 있습니다.

프로세스를 만드는 방법은 간단합니다. 쓰레드를 생성했던것처럼
multiprocessing패키지에서 Process크래스로부터 객체를 만들어 각각의 프로세스에서 50000000 씩 증가시켜서 결과를 큐에 삽입해 최종으로 1억까지 증가시키는 코드입니다.

from multiprocessing import Process, Queue
import time

def worker(id, number, q):
    increased_number = 0

    for i in range(number):
        increased_number += 1
    
    q.put(increased_number)

    return


if __name__ == "__main__":

    start_time = time.time()
    q = Queue()

    th1 = Process(target=worker, args=(1, 50000000, q))
    th2 = Process(target=worker, args=(2, 50000000, q))

    th1.start()
    th2.start()
    th1.join()
    th2.join()


    print("--- %s seconds ---" % (time.time() - start_time))
    q.put('exit')

    total = 0
    while True:
        tmp = q.get()
        if tmp == 'exit':
            break
        else:
            total += tmp

    print("total_number=",end=""), print(total)
    print("end of main")

좋은 웹페이지 즐겨찾기