Python - Взаимодействие между потоками

Привет, будущие маги Python! Сегодня мы отправляемся в захватывающее путешествие в мир взаимодействия между потоками в Python. Не волнуйтесь, если вы новичок в программировании – я стану вашим дружелюбным гидом и объясню все шаг за шагом. Поехали!

Python - Inter-thread Communication

Что такое взаимодействие между потоками?

Перед тем как погружаться в детали, давайте поймем, что такое взаимодействие между потоками. Представьте себе, что вы работаете в команде над большим проектом. Вы все работаете над различными частями, но иногда вам нужно делиться информацией или координировать свои усилия. Точно так же работают потоки в программе, и взаимодействие между потоками – это как они "разговаривают" друг с другом.

Объект события

Начнем с одного из самых простых способов взаимодействия потоков: объект события.

Что такое объект события?

Объект события похож на флаг, который может быть установлен или сброшен. Потоки могут ждать, пока этот флаг не будет установлен, прежде чем продолжить. Это немного похоже на ожидание зеленого света перед переходом улицы.

Как использовать объект события

Посмотрим на простой пример:

import threading
import time

def waiter(event):
print("Ожидающий: Я жду, пока событие не будет установлено...")
event.wait()
print("Ожидающий: Событие было установлено! Теперь я могу продолжить.")

def setter(event):
print("Устанавливающий: Я установлю событие через 3 секунды...")
time.sleep(3)
event.set()
print("Устанавливающий: Я установил событие!")

# Создание объекта события
e = threading.Event()

# Создание и запуск потоков
t1 = threading.Thread(target=waiter, args=(e,))
t2 = threading.Thread(target=setter, args=(e,))

t1.start()
t2.start()

# Ожидание завершения обоих потоков
t1.join()
t2.join()

print("Основной поток: Все выполнено!")

Разберем это:

  1. Мы импортируем модуль threading для создания потоков и модуль time для добавления задержек.
  2. Мы определяем две функции: waiter и setter.
  3. Функция waiter ждет, пока событие не будет установлено, используя event.wait().
  4. Функция setter ждет 3 секунды (симуляция работы) и затем устанавливает событие, используя event.set().
  5. Мы создаем объект события e.
  6. Мы создаем два потока, один для каждой функции, передавая объект события обоим.
  7. Мы запускаем оба потока и затем используем join() для ожидания их завершения.

Когда вы запустите это, вы увидите, как ожидающий ждет, а затем после 3 секунд устанавливающий устанавливает событие, и ожидающий продолжает.

Объект условия

Теперь поднимемся на уровень выше и рассмотрим объект Условия. Это как более сложный двоюродный брат объекта события.

Что такое объект условия?

Объект условия позволяет потокам ждать, пока определенные условия не станут истинными. Это как ожидание конкретного человека на вечеринке перед началом игр.

Как использовать объект условия

Вот пример использования объекта условия:

import threading
import time
import random

# Общий ресурс
items = []
condition = threading.Condition()

def producer():
global items
for i in range(5):
time.sleep(random.random())  # Симуляция различных времен производства
with condition:
items.append(f"Элемент {i}")
print(f"Производитель добавил Элемент {i}")
condition.notify()  # Уведомить потребителя, что элемент доступен

def consumer():
global items
while True:
with condition:
while not items:
print("Потребитель ждет...")
condition.wait()
item = items.pop(0)
print(f"Потребитель удалил {item}")
time.sleep(random.random())  # Симуляция различных времен потребления

# Создание и запуск потоков
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

producer_thread.start()
consumer_thread.start()

# Ожидание завершения производителя
producer_thread.join()

# Остановка потребителя (он в бесконечном цикле)
consumer_thread.daemon = True

Разберем это:

  1. Мы создаем общий ресурс items и объект условия.
  2. Функция producer добавляет элементы в список и уведомляет потребителя.
  3. Функция consumer ждет, пока элементы не станут доступны, затем удаляет и "потребляет" их.
  4. Мы используем with condition: для автоматического получения и освобождения блокировки условия.
  5. condition.wait() освобождает блокировку и ждет уведомления.
  6. condition.notify() пробуждает один ожидающий поток.

Этот пример демонстрирует классическую сценарий производитель-потребитель, где один поток производит элементы, а другой потребляет их.

Сравнение объектов события и условия

Вот быстрое сравнение объектов события и условия:

Характеристика Событие Условие
Назначение Простая сигнализация Сложная синхронизация
Состояние Двоичное (установлен/сброшен) Может иметь множество состояний
Ожидание Потоки ждут установки события Потоки ждут определенных условий
Уведомление Все ожидающие потоки уведомляются Может уведомить один или все ожидающие потоки
Применение Простые сценарии "проход/нет" Проблемы производитель-потребитель, сложная синхронизация

Заключение

Поздравляем! Вы только что сделали свои первые шаги в мир взаимодействия между потоками в Python. Мы покрыли объект события для простой сигнализации и объект условия для более сложных сценариев синхронизации.

Помните, как и при изучении любого нового языка, практика совершенствует мастера. Попробуйте написать свои собственные программы с использованием этих объектов. Можете создать простую систему чата, где потоки представляют различных пользователей, или симулировать систему светофора с использованием событий.

Взаимодействие между потоками может показаться сложным сначала, но с временем и практикой вы сможете направлять потоки, как маэстро ведет оркестр. Продолжайте программировать, учиться и, что самое важное, наслаждайтесь процессом!

Credits: Image by storyset