스레드 동기화
스레드 동기화는 두 개 이상의 동시 스레드가 중요 섹션으로 알려진 프로그램 세그먼트에 동시에 액세스하지 않는다는 것을 확신 할 수있는 방법으로 정의 할 수 있습니다. 반면에 중요한 섹션은 공유 리소스에 액세스하는 프로그램의 일부입니다. 따라서 동기화는 두 개 이상의 스레드가 동시에 리소스에 액세스하여 서로 인터페이스하지 않도록하는 프로세스라고 말할 수 있습니다. 아래 다이어그램은 4 개의 스레드가 프로그램의 중요 섹션에 동시에 액세스하려고 시도하는 것을 보여줍니다.
더 명확하게하기 위해 두 개 이상의 스레드가 동시에 목록에 개체를 추가하려고한다고 가정합니다. 이 행위는 하나 또는 모든 객체를 삭제하거나 목록의 상태를 완전히 손상 시키므로 성공적인 종료로 이어질 수 없습니다. 여기서 동기화의 역할은 한 번에 하나의 스레드 만 목록에 액세스 할 수 있다는 것입니다.
스레드 동기화 문제
동시 프로그래밍을 구현하거나 동기화 기본 요소를 적용하는 동안 문제가 발생할 수 있습니다. 이 섹션에서는 두 가지 주요 문제에 대해 설명합니다. 문제는-
- Deadlock
- 경쟁 조건
경쟁 조건
이것은 동시 프로그래밍의 주요 문제 중 하나입니다. 공유 리소스에 대한 동시 액세스는 경쟁 조건으로 이어질 수 있습니다. 경쟁 조건은 두 개 이상의 스레드가 공유 데이터에 액세스 한 다음 그 값을 동시에 변경하려고 할 때 조건이 발생하는 것으로 정의 할 수 있습니다. 이로 인해 변수 값은 예측할 수없고 프로세스의 컨텍스트 전환 타이밍에 따라 달라질 수 있습니다.
예
경쟁 조건의 개념을 이해하려면이 예를 고려하십시오.
Step 1 −이 단계에서는 스레딩 모듈을 가져와야합니다 −
import threading
Step 2 − 이제 전역 변수, 즉 x를 그 값과 함께 0으로 정의합니다 −
x = 0
Step 3 − 이제 우리는 increment_global() 이 전역 함수 x에서 1 씩 증가하는 함수
def increment_global():
global x
x += 1
Step 4 −이 단계에서는 taskofThread()함수는 지정된 횟수만큼 increment_global () 함수를 호출합니다. 이 예에서는 50000 배입니다.
def taskofThread():
for _ in range(50000):
increment_global()
Step 5− 이제 스레드 t1 및 t2가 생성되는 main () 함수를 정의합니다. 둘 다 start () 함수의 도움으로 시작되고 join () 함수의 도움으로 작업이 끝날 때까지 기다립니다.
def main():
global x
x = 0
t1 = threading.Thread(target= taskofThread)
t2 = threading.Thread(target= taskofThread)
t1.start()
t2.start()
t1.join()
t2.join()
Step 6− 이제 main () 함수를 호출하려는 반복 횟수에 대한 범위를 제공해야합니다. 여기서는 5 번 호출합니다.
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
아래 표시된 출력에서 각 반복 후 x의 값이 100000으로 예상되므로 경쟁 조건의 효과를 볼 수 있습니다. 그러나 값에는 많은 변동이 있습니다. 이는 공유 전역 변수 x에 대한 스레드의 동시 액세스 때문입니다.
산출
x = 100000 after Iteration 0
x = 54034 after Iteration 1
x = 80230 after Iteration 2
x = 93602 after Iteration 3
x = 93289 after Iteration 4
잠금을 사용하여 경쟁 조건 처리
위 프로그램에서 경합 상태의 영향을 확인했듯이 여러 스레드 간의 경합 상태를 처리 할 수있는 동기화 도구가 필요합니다. Python에서<threading>모듈은 경쟁 조건을 처리하기 위해 Lock 클래스를 제공합니다. 또한Lock클래스는 여러 스레드 간의 경합 상태를 처리 할 수있는 다양한 방법을 제공합니다. 방법은 아래에 설명되어 있습니다-
acquire () 메서드
이 방법은 잠금을 획득, 즉 차단하는 데 사용됩니다. 잠금은 다음 참 또는 거짓 값에 따라 차단 또는 차단되지 않을 수 있습니다.
With value set to True − acquire () 메서드가 기본 인수 인 True로 호출되면 잠금이 해제 될 때까지 스레드 실행이 차단됩니다.
With value set to False − acquire () 메서드가 기본 인수가 아닌 False로 호출되면 스레드 실행은 true로 설정 될 때까지 즉 잠길 때까지 차단되지 않습니다.
release () 메서드
이 방법은 잠금을 해제하는 데 사용됩니다. 다음은이 방법과 관련된 몇 가지 중요한 작업입니다.
자물쇠가 잠겨 있으면 release()방법은 그것을 잠금 해제합니다. 그 일은 둘 이상의 스레드가 차단되고 잠금이 잠금 해제되기를 기다리는 경우 정확히 하나의 스레드가 진행되도록 허용하는 것입니다.
그것은 ThreadError 잠금이 이미 잠금 해제 된 경우.
이제 우리는 경쟁 조건을 피하기 위해 잠금 클래스와 메서드를 사용하여 위의 프로그램을 다시 작성할 수 있습니다. lock 인수를 사용하여 taskofThread () 메서드를 정의한 다음, 경쟁 조건을 피하기 위해 잠금을 차단 및 비 차단하기 위해 acquire () 및 release () 메서드를 사용해야합니다.
예
다음은 경쟁 조건을 처리하기위한 잠금 개념을 이해하는 파이썬 프로그램의 예입니다.
import threading
x = 0
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target = taskofThread, args = (lock,))
t2 = threading.Thread(target = taskofThread, args = (lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
다음 출력은 경쟁 조건의 효과가 무시되었음을 보여줍니다. x의 값은 모든 반복 후 이제 100000이며, 이는이 프로그램의 기대에 따른 것입니다.
산출
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
Deadlocks-식사 철학자 문제
교착 상태는 동시 시스템을 설계하는 동안 직면 할 수있는 문제입니다. 다음과 같이 식사 철학자 문제의 도움으로이 문제를 설명 할 수 있습니다.
Edsger Dijkstra는 원래 교착 상태라는 동시 시스템의 가장 큰 문제 중 하나의 유명한 삽화 중 하나 인 식사 철학자 문제를 소개했습니다.
이 문제에는 5 명의 유명한 철학자가 원탁에 앉아 그릇에서 음식을 먹고 있습니다. 다섯 철학자가 음식을 먹을 때 사용할 수있는 포크는 다섯 가지입니다. 그러나 철학자들은 음식을 먹을 때 두 개의 포크를 동시에 사용하기로 결정했습니다.
이제 철학자들에게는 두 가지 주요 조건이 있습니다. 첫째, 각 철학자들은 먹거나 생각하는 상태에있을 수 있고 둘째, 그들은 먼저 포크, 즉 왼쪽과 오른쪽을 모두 얻어야합니다. 다섯 명의 철학자가 각각 동시에 왼쪽 포크를 고를 때 문제가 발생합니다. 이제 그들은 모두 올바른 포크가 무료가되기를 기다리고 있지만 음식을 먹고 올바른 포크를 사용할 수 없을 때까지 포크를 포기하지 않을 것입니다. 따라서 저녁 식사 테이블에 교착 상태가 발생합니다.
동시 시스템의 교착 상태
이제 보시면 동시 시스템에서도 동일한 문제가 발생할 수 있습니다. 위의 예에서 포크는 시스템 리소스이며 각 철학자는 리소스를 얻기 위해 경쟁하는 프로세스를 나타낼 수 있습니다.
Python 프로그램을 사용한 솔루션
이 문제의 해결책은 철학자들을 두 가지 유형으로 나누면 찾을 수 있습니다. greedy philosophers 과 generous philosophers. 주로 탐욕스러운 철학자는 왼쪽 포크를 집어 들고 거기에 올 때까지 기다릴 것입니다. 그런 다음 올바른 포크가있을 때까지 기다렸다가 집어 들고 먹고 내려 놓습니다. 반면에 관대 한 철학자는 왼쪽 포크를 집 으려고 노력할 것이고, 그것이 없으면 잠시 후에 다시 시도 할 것입니다. 왼쪽 포크를 얻으면 오른쪽 포크를 얻으려고합니다. 그들이 올바른 포크를 얻게된다면 두 포크를 모두 먹고 놓을 것입니다. 그러나 오른쪽 포크를 얻지 못하면 왼쪽 포크를 해제합니다.
예
다음 Python 프로그램은 식사 철학자 문제에 대한 해결책을 찾는 데 도움이됩니다.
import threading
import random
import time
class DiningPhilosopher(threading.Thread):
running = True
def __init__(self, xname, Leftfork, Rightfork):
threading.Thread.__init__(self)
self.name = xname
self.Leftfork = Leftfork
self.Rightfork = Rightfork
def run(self):
while(self.running):
time.sleep( random.uniform(3,13))
print ('%s is hungry.' % self.name)
self.dine()
def dine(self):
fork1, fork2 = self.Leftfork, self.Rightfork
while self.running:
fork1.acquire(True)
locked = fork2.acquire(False)
if locked: break
fork1.release()
print ('%s swaps forks' % self.name)
fork1, fork2 = fork2, fork1
else:
return
self.dining()
fork2.release()
fork1.release()
def dining(self):
print ('%s starts eating '% self.name)
time.sleep(random.uniform(1,10))
print ('%s finishes eating and now thinking.' % self.name)
def Dining_Philosophers():
forks = [threading.Lock() for n in range(5)]
philosopherNames = ('1st','2nd','3rd','4th', '5th')
philosophers= [DiningPhilosopher(philosopherNames[i], forks[i%5], forks[(i+1)%5]) \
for i in range(5)]
random.seed()
DiningPhilosopher.running = True
for p in philosophers: p.start()
time.sleep(30)
DiningPhilosopher.running = False
print (" It is finishing.")
Dining_Philosophers()
위의 프로그램은 탐욕스럽고 관대 한 철학자의 개념을 사용합니다. 이 프로그램은 또한acquire() 과 release() 의 방법 Lock 의 클래스 <threading>기준 치수. 다음 출력에서 솔루션을 볼 수 있습니다.
산출
4th is hungry.
4th starts eating
1st is hungry.
1st starts eating
2nd is hungry.
5th is hungry.
3rd is hungry.
1st finishes eating and now thinking.3rd swaps forks
2nd starts eating
4th finishes eating and now thinking.
3rd swaps forks5th starts eating
5th finishes eating and now thinking.
4th is hungry.
4th starts eating
2nd finishes eating and now thinking.
3rd swaps forks
1st is hungry.
1st starts eating
4th finishes eating and now thinking.
3rd starts eating
5th is hungry.
5th swaps forks
1st finishes eating and now thinking.
5th starts eating
2nd is hungry.
2nd swaps forks
4th is hungry.
5th finishes eating and now thinking.
3rd finishes eating and now thinking.
2nd starts eating 4th starts eating
It is finishing.