해당 포스팅은 지난 시간에 진행된 쓰레드에 관련된 문제를 해결하는 포스팅입니다.
2023.11.17 - [Python/Tip & Etc] - python Thread(쓰레드) 병렬처리
1. 문제 다시보기
Question.
A선수와 B 선수가 100m 달리기를 한다. 두 선수는 컨디션에 따라 결과를 예측하기 어려운 라이벌로 A 선수는 평균 4~7m/s, B선수는 평균 5~6m/s의 기록을 가지고 있다.
오늘 두 선수의 속도는 최소값과 최대 값 사이의 임의의 정수 값을 가질 때, 결과를 출력하는 코드를 작성해보세요.
결과 풀이에 앞서 지난 포스팅에서는 매서드를 이용한 방법으로 쓰레드를 처리하였는데,
앞선 포스팅과 같이 함수를 사용할 수도 있고, 클래스로도 사용할 수 있다.
오늘은 이 2가지를 모두 살펴보겠다.
2. 매서드(함수)로 해결하기
- 함수를 동시에 처리하기 위해 threading 라이브러리를 사용하고,
- 경기일 컨디션에 따른 속도를 임의로 지정하기 위해 random 라이브러리를 사용한다.
- 1초가 경과할 때마다 진행 상황을 보여주기 위해 time 라이브러리까지 준비하면 준비는 끝이난다.
- finish 라는 전역변수를 만들어 경기가 끝이 났는지(1등이 결승선을 지나쳤는지) 확인을 하고,
- 1등이 결정되면 해당 선수의 이름을 finish_player 변수 값에 넣는다.
import threading, time, random
finish = False
finish_player = ''
def a_player(speed):
global finish
global finish_player
run_distance = 0
lap_time = 0
print('A 선수 Start')
while run_distance < 100 and finish == False:
lap_time += 1
time.sleep(1)
run_distance += speed
print(f'{lap_time}초 : A 선수가 {run_distance}m 지점을 지나갑니다.')
print(f'A 선수가 결승선에 도착했습니다.')
if finish == False:
finish = True
global finish_player
finish_player = 'A 선수'
def b_player(speed):
global finish
global finish_player
run_distance = 0
lap_time = 0
print('B 선수 Start')
while run_distance < 100 and finish == False:
lap_time += 1
time.sleep(1)
run_distance += speed
print(f'{lap_time}초 : B 선수가 {run_distance}m 지점을 지나갑니다.')
print(f'B 선수가 결승선에 도착했습니다.')
if finish == False:
finish = True
global finish_player
finish_player = 'B 선수'
def congratulation():
global finish_player
print('축하합니다.')
print(f'우승자는 {finish_player} 선수입니다.')
ran_a_speed = random.randint(4,7) # A 선수의 임의 값 속도
thread_a = threading.Thread(target=a_player, args=(ran_a_speed,))
thread_a.daemon = True
ran_b_speed = random.randint(5,6) # B 선수의 임의 값 속도
thread_b = threading.Thread(target=b_player, args=(ran_b_speed,))
thread_b.daemon = True
thread_a.start()
thread_b.start()
thread_a.join()
thread_b.join()
congratulation()
- 경기 결과는 실행 때마다 달라질 수 있다.
- 경기 결과를 반복하다보면, 두 선수의 속도가 같은 값이 될 때가 있는데,
속도가 같으면 무승부가 나야 하지만, 결과 값을 보면 같은 20초에 100m 지점에 도착했지만
특정 선수가 우승자라고 나타나는 것을 볼 수 있다.
- GIL(Global Interpreter Lock) 이라는 제어 방법에 따라 생기는데,
데이터의 일관성 있는 결과(메모리의 안정성)을 유지하기 위해 GIL이 사용되지만 이로 인해
쓰레드를 사용하더라도 완전히 2가지가 동시에 진행되지 않음을 뜻한다.
3. 클래스(class)로 해결하기
- 위에 코드에서 def a_player 와 def b_player를 비교해보면 거의 모든 내용이 유사하다.
- 이럴 때는 코드의 재사용을 위해 클래스(class)를 사용하는 방법이 좋은데,
Player_info 클래스를 생성할 때 threading.Thread 클래스를 상속 받으면 된다.
- 가장 핵심적인 부분은 Player_info 클래스 내의 run 함수인데,
오버라이딩이라는 기능을 사용하기 위해 꼭 run이라는 매서드로 이름을 생성해야 한다.
- 오버라이딩(overriding)이란 기반 클래스(부모 클래스 / threading.Thread)의 매서드를 무시하기 위해,
동일한 이름을 가진 매서드를 자식클래스(Player_info)에 생성하는 것을 말하는데,
이렇게 오버라이딩을 하게되면 run을 실행하더라도 부모클래스(threading.Thread)의 run 매서드가 실행되는 것이 아닌
자식클래스(Player_info)의 run이 실행되게 된다.
- 생성된 a_palyer(56라인)와 b_player(59라인) 객체는 기존의 매서드(함수) 방식과 달리 threading.Thread를 상속 받고 있기 때문에 해당 객체에 start()와 join() 매서드를 실행하더로도 동작하는 것을 확인할 수 있다.
- 이때 동작을 하게 되는 start() 매서드는 run() 매서드를 실행하게 되는데,
우리가 오버라이딩으로 run 매서드를 새로 정의했기 때문에 Player_info의 run 매서드를 사용하게 된다.
import random, time, threading
finish = False
finish_player = ''
all_player = []
class Player_info(threading.Thread): #threading.Thread 상속
def __init__(self, player_name, speed_range):
threading.Thread.__init__(self)
self.player_name = player_name # 선수 이름
global all_player
all_player.append(self.player_name)
self.speed_range = speed_range # 선수 속도 범위
self.player_goalin = False # 결승선 통과 Flag
def set_today_speed(self):
self.player_goalin = False # 결승선 통과 Flag
self.today_speed = random.randint(int(self.speed_range[0]), int(self.speed_range[1]))
#return(str(self.today_speed()))
def run(self): # threading.Thread 의 run 매서드 오버라이딩
self.run_distance = 0
self.lap_time = 0
print(f'{self.player_name} 선수 Start')
while self.run_distance < 100 and self.player_goalin == False:
self.lap_time += 1
time.sleep(1)
self.run_distance += self.today_speed
print(f'{self.lap_time}초 : {self.player_name} 선수가 {self.run_distance}m 지점을 지나갑니다.')
print(f'{self.player_name} 선수가 결승선에 도착했습니다.')
global finish
global finish_player
if finish == False:
finish = True
finish_player = self.player_name
def congratulation():
global finish_player
print('축하합니다.')
print(f'우승자는 {finish_player} 선수입니다.')
a_player = Player_info('홍길동',(4,7))
a_player.set_today_speed()
b_player = Player_info('전우치',(5,6))
b_player.set_today_speed()
a_player.start()
b_player.start()
a_player.join()
b_player.join()
congratulation()
4. 승률 확인하기
- 10번의 반복을 통해 어느 선수의 승률이 더 높은지 확인해보겠습니다.
- 사실 ranint로 생성되는 선수별 set_today_speed 만 확인하여도 결과는 정해지는 걸테지만,
재미삼아 진행해보겠습니다.
( 실제 테스트간 두 선수가 같은 속도 일 때, 5 또는 6일때 무승부가 아닌 먼저 처리되는 서브쓰레드의 선수가 승리로 반영되어(위에 언급된 GIL.....로 인해) 해당 속도가 같을 때는 제외 하고 진행하겠습니다.)
- 위의 코드에서 반복문을 진행하는 동안 몇가지 변수 값만 초기화를 진행하고 결과를 확인해보겠습니다.
- 한번 생성된 Thread 객체는 start를 1회 밖에 진행할 수 없기 때문에 a_player, b_player를 생성하는 객체는 while문 안에 포함되었습니다.
( *해당 코드가 while 문 보다 상위에 작성되면 2회째 실행을 할 때, 'threads can only be started once' 라는 Error 가 발생합니다.)
from tkinter import *
from tkinter import ttk
import random, time, threading
finish = False
finish_player = ''
all_player = []
class Player_info(threading.Thread):
def __init__(self, player_name, speed_range):
threading.Thread.__init__(self)
self.player_name = player_name # 선수 이름
global all_player
all_player.append(self.player_name)
self.speed_range = speed_range # 선수 속도 범위
self.player_goalin = False # 결승선 통과 Flag
def set_today_speed(self):
self.player_goalin = False # 결승선 통과 Flag
self.today_speed = random.randint(int(self.speed_range[0]), int(self.speed_range[1]))
return(str(self.today_speed))
def run(self):
self.run_distance = 0
self.lap_time = 0
print(f'{self.player_name} 선수 Start')
while self.run_distance < 100 and self.player_goalin == False:
self.lap_time += 1
time.sleep(1)
self.run_distance += self.today_speed
print(f'{self.lap_time}초 : {self.player_name} 선수가 {self.run_distance}m 지점을 지나갑니다.')
print(f'{self.player_name} 선수가 결승선에 도착했습니다.')
global finish
global finish_player
if finish == False:
finish = True
finish_player = self.player_name
def congratulation():
global finish_player
print('축하합니다.')
print(f'우승자는 {finish_player} 선수입니다.')
match_count = 0
win_a = 0 # A 선수(홍길동)의 승리 횟수
win_b = 0 # B 선수(전우치)의 승리 횟수
while match_count < 10:
finish = False # 경기 초기화
a_player = Player_info('홍길동',(4,7))
b_player = Player_info('전우치',(5,6))
today_a = a_player.set_today_speed() # 해당 경기일 속도 초기화
today_b = b_player.set_today_speed() # 해당 경기일 속도 초기화
#print(f'{today_a} : {today_b}')
if today_a != today_b:
print(f'{match_count+1}번째 경기를 시작합니다.')
a_player.start()
b_player.start()
a_player.join()
b_player.join()
congratulation()
if finish_player == '홍길동':
win_a += 1
elif finish_player == '전우치':
win_b += 1
match_count += 1
print('경기가 종료되었습니다. \n\n')
print(f'홍길동선수와 전우치선수의 승률은 {win_a} 대 {win_b} 입니다.')
- 결과는 그날의 컨디션에 따라 기복은 있으나 최대 속도가 더 뛰어난 홍길동 선수보다,
기복 없이 일정한 역량을 발휘하는 전우치 선수가 더 높은 승률을 보이는 것으로 결과가 마무리되었습니다.
- 어떠신가요? 파이썬을 배웠을 뿐인대, 이런 결과를 도출해낼 수 있다는 것도 신기하지 않으신가요?
- 파이썬은 도구일뿐이라는 말도 어느정도 체감이 되지는 않았을까라는 생각을 갖게되는 오늘의 포스팅입니다.
'Python > Tip & Etc' 카테고리의 다른 글
[python] if __name__ == '__main__' (1) | 2023.11.27 |
---|---|
python Thread(쓰레드) 병렬처리 (0) | 2023.11.17 |
파이썬(python) 폴더 만들기(없는 디렉토리 만들기) (0) | 2023.10.27 |