В многопроцессных Python приложениях, блокировки и семафоры играют ключевую роль в синхронизации доступа к общим ресурсам и предотвращении гонок данных.
Блокировки (Locks): Представляют собой наиболее простой механизм синхронизации. Они позволяют только одному процессу в каждый момент времени владеть блокировкой. Процесс, желающий получить доступ к защищенному ресурсу, сначала пытается получить блокировку (acquire). Если блокировка свободна, процесс ее получает и продолжает работу. Если блокировка занята другим процессом, текущий процесс блокируется и ждет, пока блокировка не будет освобождена (release). После завершения работы с ресурсом, процесс освобождает блокировку, позволяя другому ожидающему процессу ее получить.
Семафоры (Semaphores): Являются более обобщенным механизмом, чем блокировки. Вместо простой "занято/свободно" блокировки, семафор имеет внутренний счетчик. Когда процесс хочет получить доступ к ресурсу, он уменьшает счетчик семафора (acquire). Если счетчик становится отрицательным, процесс блокируется до тех пор, пока счетчик не станет положительным. Когда процесс освобождает ресурс, он увеличивает счетчик семафора (release), что может разблокировать один из ожидающих процессов. Семафоры полезны, когда нужно ограничить количество процессов, одновременно получающих доступ к ресурсу (например, количество одновременных подключений к базе данных).
Различия между блокировками и семафорами:
Реализация в Python: В Python для многопроцессной синхронизации обычно используются классы `Lock` и `Semaphore` из модуля `multiprocessing`. Эти классы обеспечивают безопасную работу с блокировками и семафорами между процессами, используя низкоуровневые механизмы операционной системы.
Важно помнить: При использовании блокировок и семафоров в многопроцессных приложениях необходимо быть осторожным, чтобы избежать взаимных блокировок (deadlocks) и гонок данных (race conditions). Необходимо тщательно проектировать логику синхронизации и использовать контекстные менеджеры (`with lock:`), чтобы гарантировать освобождение блокировок, даже если в процессе выполнения кода произошла ошибка.
Пример с Lock:
import multiprocessing
def worker(lock, shared_data):
with lock: # Захватываем блокировку
# Критическая секция: работа с общими данными
shared_data.value += 1
print(f"Process {multiprocessing.current_process().name}: shared_data = {shared_data.value}")
if __name__ == '__main__':
lock = multiprocessing.Lock()
shared_data = multiprocessing.Value('i', 0) # 'i' обозначает integer
processes = []
for i in range(3):
p = multiprocessing.Process(target=worker, args=(lock, shared_data), name=f"Process-{i}")
processes.append(p)
p.start()
for p in processes:
p.join()
print(f"Final shared_data value: {shared_data.value}")
Пример с Semaphore:
import multiprocessing
import time
def worker(semaphore, worker_id):
with semaphore:
print(f"Worker {worker_id} acquired semaphore. Working...")
time.sleep(2) # Имитация работы
print(f"Worker {worker_id} released semaphore.")
if __name__ == "__main__":
semaphore = multiprocessing.Semaphore(2) # Только 2 процесса одновременно
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(semaphore, i))
processes.append(p)
p.start()
for p in processes:
p.join()
print("All workers finished.")