Python - Синхронизация потоков

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

Python - Synchronizing Threads

Синхронизация потоков с использованием блокировок

Начнем с самого базового инструмента в нашем наборе синхронизации: блокировки. Представьте блокировку как знак "не беспокоить" на двери номера в отеле. Когда поток получает блокировку, это как если бы он вывешивал этот знак, говоря другим потокам: "Эй, я занят здесь!"

Вот простой пример для иллюстрации этого концепта:

import threading
import time

# Общий ресурс
счетчик = 0

# Создание блокировки
блокировка = threading.Lock()

def увеличить():
global счетчик
for _ in range(100000):
блокировка.acquire()
счетчик += 1
блокировка.release()

# Создание двух потоков
поток1 = threading.Thread(target=увеличить)
поток2 = threading.Thread(target=увеличить)

# Запуск потоков
поток1.start()
поток2.start()

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

print(f"Конечное значение счетчика: {счетчик}")

В этом примере у нас есть общий ресурс счетчик, который два потока пытаются увеличить. Без блокировки мы можем столкнуться с состоянием гонки, когда оба потока попытаются увеличить счетчик одновременно, что может привести к неправильным результатам.

Используя блокировка.acquire() перед изменением счетчика и блокировка.release() после, мы обеспечиваем, что только один поток может увеличить счетчик за один раз. Это как передача эстафеты в беге на длинные дистанции — только поток, держащий эстафету (блокировку), может бежать (изменять общий ресурс).

Объекты Condition для синхронизации потоков Python

Теперь поднимем нашу игру на новый уровень с объектами Condition. Это как сложные светофоры для наших потоков, позволяющие более сложную координацию.

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

import threading
import time
import random

# Общий буфер
буфер = []
МАКС_РАЗМЕР = 5

# Создание объекта Condition
условие = threading.Condition()

def производитель():
global буфер
while True:
with условие:
while len(буфер) == МАКС_РАЗМЕР:
print("Буфер полон, производитель ждет...")
условие.wait()
элемент = random.randint(1, 100)
буфер.append(элемент)
print(f"Произведено: {элемент}")
условие.notify()
time.sleep(random.random())

def потребитель():
global буфер
while True:
with условие:
while len(буфер) == 0:
print("Буфер пуст, потребитель ждет...")
условие.wait()
элемент = буфер.pop(0)
print(f"Потреблено: {элемент}")
условие.notify()
time.sleep(random.random())

# Создание потоков производителя и потребителя
производитель_поток = threading.Thread(target=производитель)
потребитель_поток = threading.Thread(target=потребитель)

# Запуск потоков
производитель_поток.start()
потребитель_поток.start()

# Даем им работать некоторое время
time.sleep(10)

В этом примере у нас есть производитель, добавляющий элементы в буфер, и потребитель, удаляющий элементы из него. Объект Condition помогает координировать их действия:

  • Производитель ждет, когда буфер полон.
  • Потребитель ждет, когда буфер пуст.
  • Они уведомляют друг друга, когда можно продолжить.

Это как хорошо поставленный танец, где объект Condition является хореографом!

Синхронизация потоков с использованием метода join()

Метод join() — это как если бы один поток ждал, пока другой не закончит свое выступление, прежде чем взять сцену. Это простой, но мощный способ синхронизации потоков.

Вот пример:

import threading
import time

def работник(имя, задержка):
print(f"{имя} начинает...")
time.sleep(задержка)
print(f"{имя} закончил!")

# Создание потоков
поток1 = threading.Thread(target=работник, args=("Поток 1", 2))
поток2 = threading.Thread(target=работник, args=("Поток 2", 4))

# Запуск потоков
поток1.start()
поток2.start()

# Ожидание завершения поток1
поток1.join()
print("Основной поток ждет после поток1")

# Ожидание завершения поток2
поток2.join()
print("Основной поток ждет после поток2")

print("Все потоки закончили!")

В этом примере основной поток запускает два рабочих потока и затем ждет каждого из них с помощью join(). Это как родитель, ждущий, пока дети не закончат уроки, прежде чем подать ужин!

Дополнительные примитивы синхронизации

Python предлагает несколько других инструментов для синхронизации потоков. Взглянем на некоторые из них:

Примитив Описание Применение
Семафор Позволяет ограниченному количеству потоков доступа к ресурсу Управление пулом подключений к базе данных
Событие Позволяет одному потоку сигналить событие другим потокам Сигнализировать о завершении задачи
Барьер Позволяет нескольким потокам ждать, пока все не достигнут определенной точки Синхронизация начала гонки

Вот быстрый пример с использованием Семафора:

import threading
import time

# Создание семафора, который позволяет 2 потокам одновременно
семафор = threading.Semaphore(2)

def работник(имя):
with семафор:
print(f"{имя} получил семафор")
time.sleep(1)
print(f"{имя} освободил семафор")

# Создание и запуск 5 потоков
потоки = []
for i in range(5):
поток = threading.Thread(target=работник, args=(f"Поток {i}",))
потоки.append(поток)
поток.start()

# Ожидание завершения всех потоков
for поток in потоки:
поток.join()

print("Все потоки закончили!")

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

Итак, это было! Мы исследовали увлекательный мир синхронизации потоков в Python. Помните, как руководство оркестром или постановка танца, синхронизация потоков — это все о координации и времени. С этими инструментами в вашем арсенале вы уже на пути к созданию гармоничных, многопоточных программ на Python. Удачи в практике, оставайтесь любознательными и счастливого кодирования!

Credits: Image by storyset