Thread (1)

teal·2023년 8월 9일
0

Python

목록 보기
1/8

만약 아래와 캐릭터가 존재한다고 하자 캐릭터는 일을 100만번하면 0.3초씩 쉬어야하는 특성을 가지고있다.

class Character:
    def __init__(self, *args, **kwargs):
        self._gold: int = 0
        self._work_count: int = 0

    def work(self, gold: int) -> None:
        self._gold += gold
        self._work_count += 1

        if self._work_count % 1000000 == 0:
            time.sleep(0.3)

    def show_gold(self) -> int:
        return self._gold

    def work_multiple_times(self, times: int, gold: int) -> None:
        for _ in range(times):
            self.work(gold)

만약 단순하게 한곳에서 1000만번 일을한다고 했을때는

my_charater = Character()
main_job = Thread(target=my_charater.work_multiple_times,
                  kwargs={"times": 10000000, "gold": 2})

start_time = time.time()

main_job.start()
main_job.join()

print(time.time()-start_time)
print(my_charater.show_gold())
5.093224287033081
20000000

위와같이 시간이걸리고 임금(?)도 정상적으로 들어온것을 확인할 수 있다.

만약 이 캐릭터가 본업과 부업을 동시에 한다고 할때는 아래처럼 thread를 이용해서 일을 시킬 수 있다.

my_charater = Character()
main_job = Thread(target=my_charater.work_multiple_times,
                  kwargs={"times": 10000000, "gold": 2})
side_job = Thread(target=my_charater.work_multiple_times,
                  kwargs={"times": 10000000, "gold": 1})
start_time = time.time()

main_job.start()
side_job.start()

main_job.join()
side_job.join()

print(time.time()-start_time)
print(my_charater.show_gold())

위와 같이 본업과 부업을 동시에 돌리면 아래와 같은 결과가 나타난다.

5.629549026489258
29171722

일단 시간이 2배가 아닌 이유는 100만번 일하고 잠깐(0.3초) 쉴때마다 다른 업무를 하기때문에 엄청난 효율(?)이 나타났다.

그런데 액수는 29171722 나머지 90만은 누가 가져간것일까?
이것은 위의 상황이 Thread Safety한 환경이 아니라서 발생한 것이다.

캐릭터의 gold라는 변수가 공유 자원으로 존재하고 두 Thread가 공유 자원에 접근하려고 Race Condition을 발생시켜 일어나는 문제점이다.

그래서 Thread Safety한 환경을 만들어주면 문제가 해결된다.
1. 각 스레드가 순서를 갖게끔 실행시키면 문제가 사라진다.

start_time = time.time()

main_job.start()
main_job.join()

side_job.start()
side_job.join()

print(time.time()-start_time)
print(my_charater.show_gold())
10.25169587135315
30000000

임금은 정상적으로 들어오긴했지만 본업과 부업을 따로 진행한 덕분에 기존 시간에서 2배가 되어버렸다. 우리는 100만번 일하고 0.3초 쉬는 그 순간까지 캐릭터에게 일을 시키고 싶다.

  1. 상호 배제(Mutual Exclusion) - Mutex
    임금을 정상적으로 받게 만드려면 상호 배제, Mutex를 통한 임계 영역(Critical Section)을 만들어주면 된다.
class Character:
    def __init__(self, *args, **kwargs):
        self._gold: int = 0
        self._work_count: int = 0
        self._work_lock: Lock = Lock()

    def work(self, gold: int) -> None:
        self._work_lock.acquire()
        self._gold += gold
        self._work_count += 1
        self._work_lock.release()
        if self._work_count % 1000000 == 0:
            time.sleep(0.3)

위처럼 중요한 순간(돈을 받는 순간)은 다른 스레드와 동시에 접근 불가능한 임계 영역을 lock을 통해 만들어줄 수 있다.

6.220713138580322
30000000

lock을 걸고 푸는 오버헤드가 발생한 덕분에 lock을 안걸때보단 시간이 좀 더 걸리긴했으나 임금이 정상적으로 들어와서 만족스러운 결과를 얻을 수 있게되었다.

profile
고양이를 키우는 백엔드 개발자

0개의 댓글