스레드 상호 통신

실생활에서 팀이 공통 작업을 수행하고 있다면 작업을 제대로 완료하기 위해 그들 사이에 소통이 있어야합니다. 스레드에도 동일한 비유가 적용됩니다. 프로그래밍에서 프로세서의 이상적인 시간을 줄이기 위해 여러 스레드를 만들고 모든 스레드에 서로 다른 하위 작업을 할당합니다. 따라서 통신 기능이 있어야하며 동기화 된 방식으로 작업을 완료하기 위해 서로 상호 작용해야합니다.

스레드 상호 통신과 관련된 다음 중요 사항을 고려하십시오.

  • No performance gain − 스레드와 프로세스 간의 적절한 통신을 달성 할 수없는 경우 동시성과 병렬성으로 인한 성능 향상은 소용이 없습니다.

  • Accomplish task properly − 스레드 간의 적절한 상호 통신 메커니즘이 없으면 할당 된 작업을 제대로 완료 할 수 없습니다.

  • More efficient than inter-process communication − 스레드 간 통신은 프로세스 내 모든 스레드가 동일한 주소 공간을 공유하고 공유 메모리를 사용할 필요가 없기 때문에 프로세스 간 통신보다 더 효율적이고 사용하기 쉽습니다.

스레드로부터 안전한 통신을위한 Python 데이터 구조

다중 스레드 코드는 한 스레드에서 다른 스레드로 정보를 전달하는 문제가 있습니다. 표준 커뮤니케이션 프리미티브는이 문제를 해결하지 못합니다. 따라서 통신 스레드로부터 안전한 통신을 위해 스레드간에 개체를 공유하려면 자체 복합 개체를 구현해야합니다. 다음은 몇 가지 데이터 구조를 변경 한 후 스레드로부터 안전한 통신을 제공합니다.

세트

스레드로부터 안전한 방식으로 집합 데이터 구조를 사용하려면 자체 잠금 메커니즘을 구현하기 위해 set 클래스를 확장해야합니다.

다음은 클래스를 확장하는 Python 예제입니다.

class extend_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(extend_class, self).__init__(*args, **kwargs)

   def add(self, elem):
      self._lock.acquire()
	  try:
      super(extend_class, self).add(elem)
      finally:
      self._lock.release()
  
   def delete(self, elem):
      self._lock.acquire()
      try:
      super(extend_class, self).delete(elem)
      finally:
      self._lock.release()

위의 예에서 이름이 extend_class 파이썬에서 더 상속 된 정의되었습니다 set class. 이 클래스의 생성자 내에 잠금 객체가 생성됩니다. 이제 두 가지 기능이 있습니다.add()delete(). 이러한 함수는 정의되고 스레드로부터 안전합니다. 그들은 둘 다에 의존합니다super 하나의 주요 예외가있는 클래스 기능.

데코레이터

스레드로부터 안전한 통신을위한 또 다른 핵심 방법은 데코레이터를 사용하는 것입니다.

데코레이터 & mminus;를 사용하는 방법을 보여주는 Python 예제를 고려하십시오.

def lock_decorator(method):

   def new_deco_method(self, *args, **kwargs):
      with self._lock:
         return method(self, *args, **kwargs)
return new_deco_method

class Decorator_class(set):
   def __init__(self, *args, **kwargs):
      self._lock = Lock()
      super(Decorator_class, self).__init__(*args, **kwargs)

   @lock_decorator
   def add(self, *args, **kwargs):
      return super(Decorator_class, self).add(elem)
   @lock_decorator
   def delete(self, *args, **kwargs):
      return super(Decorator_class, self).delete(elem)

위의 예에서 lock_decorator라는 데코레이터 메서드가 정의되어 Python 메서드 클래스에서 상속되었습니다. 그런 다음이 클래스의 생성자 내에 잠금 객체가 생성됩니다. 이제 add ()와 delete ()의 두 가지 함수가 있습니다. 이러한 함수는 정의되고 스레드로부터 안전합니다. 둘 다 하나의 주요 예외를 제외하고 수퍼 클래스 기능에 의존합니다.

기울기

목록 데이터 구조는 스레드로부터 안전하고 임시 메모리 내 저장을위한 빠르고 쉬운 구조입니다. Cpython에서 GIL은 이들에 대한 동시 액세스로부터 보호합니다. 목록이 스레드로부터 안전하다는 것을 알게되면서 목록에있는 데이터는 어떻습니까? 실제로 목록의 데이터는 보호되지 않습니다. 예를 들면L.append(x)다른 스레드가 동일한 작업을 시도하는 경우 예상 결과를 반환한다고 보장하지 않습니다. 왜냐하면append() 원자 적 연산이고 스레드로부터 안전하지만 다른 스레드가 목록의 데이터를 동시에 수정하려고 시도하므로 출력에서 ​​경쟁 조건의 부작용을 볼 수 있습니다.

이러한 종류의 문제를 해결하고 데이터를 안전하게 수정하려면 적절한 잠금 메커니즘을 구현해야합니다.이를 통해 여러 스레드가 잠재적으로 경쟁 조건에 빠질 수 없도록 보장합니다. 적절한 잠금 메커니즘을 구현하기 위해 이전 예제에서했던 것처럼 클래스를 확장 할 수 있습니다.

목록에 대한 다른 원자 연산은 다음과 같습니다.

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

여기-

  • L, L1, L2는 모두 목록입니다.
  • D, D1, D2는 dicts입니다.
  • x, y는 객체입니다.
  • i, j는 정수입니다.

대기열

목록의 데이터가 보호되지 않으면 결과에 직면해야 할 수도 있습니다. 경쟁 조건의 잘못된 데이터 항목을 가져 오거나 삭제할 수 있습니다. 그렇기 때문에 큐 데이터 구조를 사용하는 것이 좋습니다. 대기열의 실제 예는 차량이 먼저 진입하고 먼저 나가는 단일 차선 일방 통행 도로 일 수 있습니다. 매표소와 버스 정류장의 대기열에 대한 실제 사례를 더 많이 볼 수 있습니다.

큐는 기본적으로 스레드로부터 안전한 데이터 구조이며 복잡한 잠금 메커니즘 구현에 대해 걱정할 필요가 없습니다. 파이썬은 우리에게 모듈을 사용하여 애플리케이션에서 다른 유형의 큐를 사용합니다.

대기열 유형

이 섹션에서는 다양한 유형의 대기열에 대해 알아 보겠습니다. Python은 다음에서 사용할 세 가지 대기열 옵션을 제공합니다.<queue> 모듈-

  • 일반 대기열 (FIFO, 선입 선출)
  • LIFO, 마지막 선입 선출
  • Priority

이후 섹션에서 다른 대기열에 대해 알아 봅니다.

일반 대기열 (FIFO, 선입 선출)

Python에서 제공하는 가장 일반적으로 사용되는 큐 구현입니다. 이 대기열 메커니즘에서 먼저 오는 사람은 먼저 서비스를받습니다. FIFO는 일반 대기열이라고도합니다. FIFO 대기열은 다음과 같이 나타낼 수 있습니다.

FIFO 대기열의 Python 구현

파이썬에서 FIFO 대기열은 단일 스레드와 다중 스레드로 구현 될 수 있습니다.

단일 스레드가있는 FIFO 대기열

단일 스레드로 FIFO 대기열을 구현하려면 Queue클래스는 기본 선입 선출 컨테이너를 구현합니다. 요소는 다음을 사용하여 시퀀스의 한 "끝"에 추가됩니다.put(), 그리고 사용하여 다른 끝에서 제거 get().

다음은 단일 스레드로 FIFO 대기열을 구현하기위한 Python 프로그램입니다.

import queue

q = queue.Queue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end = " ")

산출

item-0 item-1 item-2 item-3 item-4 item-5 item-6 item-7

출력은 위의 프로그램이 단일 스레드를 사용하여 요소가 삽입 된 순서대로 큐에서 제거되었음을 보여줍니다.

여러 스레드가있는 FIFO 대기열

다중 스레드로 FIFO를 구현하려면 큐 모듈에서 확장 된 myqueue () 함수를 정의해야합니다. get () 및 put () 메서드의 작동은 단일 스레드로 FIFO 대기열을 구현하는 동안 위에서 설명한 것과 동일합니다. 그런 다음 다중 스레드로 만들려면 스레드를 선언하고 인스턴스화해야합니다. 이 스레드는 FIFO 방식으로 대기열을 소비합니다.

다음은 여러 스레드가있는 FIFO 대기열 구현을위한 Python 프로그램입니다.

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
   item = queue.get()
   if item is None:
   break
   print("{} removed {} from the queue".format(threading.current_thread(), item))
   queue.task_done()
   time.sleep(2)
q = queue.Queue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

산출

<Thread(Thread-3654, started 5044)> removed 0 from the queue
<Thread(Thread-3655, started 3144)> removed 1 from the queue
<Thread(Thread-3656, started 6996)> removed 2 from the queue
<Thread(Thread-3657, started 2672)> removed 3 from the queue
<Thread(Thread-3654, started 5044)> removed 4 from the queue

LIFO, Last in First Out 대기열

이 대기열은 FIFO (선입 선출) 대기열과 완전히 반대되는 비유를 사용합니다. 이 대기열 메커니즘에서는 마지막에 오는 사람이 먼저 서비스를받습니다. 이것은 스택 데이터 구조를 구현하는 것과 유사합니다. LIFO 대기열은 인공 지능 알고리즘과 같은 깊이 우선 검색을 구현하는 동안 유용합니다.

LIFO 대기열의 Python 구현

파이썬에서 LIFO 대기열은 단일 스레드와 다중 스레드로 구현 될 수 있습니다.

단일 스레드가있는 LIFO 대기열

단일 스레드로 LIFO 대기열을 구현하려면 Queue 클래스는 구조를 사용하여 기본 후입 선출 컨테이너를 구현합니다. Queue.LifoQueue. 자, 부름에put(), 요소는 컨테이너의 헤드에 추가되고 사용시에도 헤드에서 제거됩니다. get().

다음은 단일 스레드로 LIFO 대기열을 구현하기위한 Python 프로그램입니다.

import queue

q = queue.LifoQueue()

for i in range(8):
   q.put("item-" + str(i))

while not q.empty():
   print (q.get(), end=" ")
Output:
item-7 item-6 item-5 item-4 item-3 item-2 item-1 item-0

출력은 위의 프로그램이 단일 스레드를 사용하여 요소가 삽입 된 순서와 반대로 큐에서 제거되었음을 보여줍니다.

여러 스레드가있는 LIFO 대기열

구현은 다중 스레드로 FIFO 대기열을 구현 한 것과 유사합니다. 유일한 차이점은Queue 구조를 사용하여 기본 후입 선출 컨테이너를 구현할 클래스 Queue.LifoQueue.

다음은 여러 스레드가있는 LIFO 대기열을 구현하기위한 Python 프로그램입니다.

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
	  print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(2)
q = queue.LifoQueue()
for i in range(5):
   q.put(i)
threads = []
for i in range(4):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

산출

<Thread(Thread-3882, started 4928)> removed 4 from the queue
<Thread(Thread-3883, started 4364)> removed 3 from the queue
<Thread(Thread-3884, started 6908)> removed 2 from the queue
<Thread(Thread-3885, started 3584)> removed 1 from the queue
<Thread(Thread-3882, started 4928)> removed 0 from the queue

우선 순위 대기열

FIFO 및 LIFO 대기열에서 항목 순서는 삽입 순서와 관련이 있습니다. 그러나 삽입 순서보다 우선 순위가 더 중요한 경우가 많습니다. 실제 사례를 살펴 보겠습니다. 공항의 보안이 다른 범주의 사람들을 확인하고 있다고 가정합니다. VVIP의 사람, 항공사 직원, 세관 직원, 카테고리는 일반인처럼 도착 기준으로 확인되는 대신 우선적으로 확인 될 수 있습니다.

우선 순위 대기열에 대해 고려해야 할 또 다른 중요한 측면은 작업 스케줄러를 개발하는 방법입니다. 한 가지 일반적인 설계는 대기열에서 우선 순위에 따라 가장 많은 상담원 작업을 제공하는 것입니다. 이 데이터 구조는 우선 순위 값에 따라 큐에서 항목을 선택하는 데 사용할 수 있습니다.

우선 순위 대기열의 Python 구현

파이썬에서 우선 순위 큐는 단일 스레드와 다중 스레드로 구현 될 수 있습니다.

단일 스레드가있는 우선 순위 대기열

단일 스레드로 우선 순위 대기열을 구현하려면 Queue 클래스는 구조를 사용하여 우선 순위 컨테이너에 작업을 구현합니다. Queue.PriorityQueue. 자, 부름에put(), 가장 낮은 값이 가장 높은 우선 순위를 갖는 값으로 요소가 추가되므로 다음을 사용하여 먼저 검색됩니다. get().

단일 스레드로 우선 순위 대기열을 구현하기 위해 다음 Python 프로그램을 고려하십시오.

import queue as Q
p_queue = Q.PriorityQueue()

p_queue.put((2, 'Urgent'))
p_queue.put((1, 'Most Urgent'))
p_queue.put((10, 'Nothing important'))
prio_queue.put((5, 'Important'))

while not p_queue.empty():
   item = p_queue.get()
   print('%s - %s' % item)

산출

1 – Most Urgent
2 - Urgent
5 - Important
10 – Nothing important

위의 출력에서 ​​큐가 우선 순위에 따라 항목을 저장했음을 알 수 있습니다. 값이 적을수록 우선 순위가 높습니다.

다중 스레드가있는 우선 순위 대기열

구현은 다중 스레드가있는 FIFO 및 LIFO 대기열의 구현과 유사합니다. 유일한 차이점은Queue 구조를 사용하여 우선 순위를 초기화하는 클래스 Queue.PriorityQueue. 또 다른 차이점은 큐가 생성되는 방식입니다. 아래 주어진 예에서는 두 개의 동일한 데이터 세트로 생성됩니다.

다음 Python 프로그램은 다중 스레드가있는 우선 순위 대기열의 구현에 도움이됩니다.

import threading
import queue
import random
import time
def myqueue(queue):
   while not queue.empty():
      item = queue.get()
      if item is None:
      break
      print("{} removed {} from the queue".format(threading.current_thread(), item))
      queue.task_done()
      time.sleep(1)
q = queue.PriorityQueue()
for i in range(5):
   q.put(i,1)

for i in range(5):
   q.put(i,1)

threads = []
for i in range(2):
   thread = threading.Thread(target=myqueue, args=(q,))
   thread.start()
   threads.append(thread)
for thread in threads:
   thread.join()

산출

<Thread(Thread-4939, started 2420)> removed 0 from the queue
<Thread(Thread-4940, started 3284)> removed 0 from the queue
<Thread(Thread-4939, started 2420)> removed 1 from the queue
<Thread(Thread-4940, started 3284)> removed 1 from the queue
<Thread(Thread-4939, started 2420)> removed 2 from the queue
<Thread(Thread-4940, started 3284)> removed 2 from the queue
<Thread(Thread-4939, started 2420)> removed 3 from the queue
<Thread(Thread-4940, started 3284)> removed 3 from the queue
<Thread(Thread-4939, started 2420)> removed 4 from the queue
<Thread(Thread-4940, started 3284)> removed 4 from the queue