Практические методы синхронизации потоков
Теперь, когда мы рассмотрели основные концепции синхронизации общих данных в потоках Python, давайте посмотрим на некоторые практические примеры использования различных примитивов синхронизации.
Использование блокировок (Locks)
Блокировки - это самые простые примитивы синхронизации в Python. Они обеспечивают, что только один поток может получить доступ к критической секции кода в определенный момент времени. Вот пример использования блокировки для защиты общего счетчика:
import threading
counter = 0
lock = threading.Lock()
def increment_counter():
global counter
for _ in range(1000000):
with lock:
counter += 1
threads = []
for _ in range(2):
t = threading.Thread(target=increment_counter)
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Final counter value: {counter}")
В этом примере объект lock
используется для обеспечения того, что только один поток может получить доступ к критической секции кода, где переменная counter
увеличивается.
Использование семафоров (Semaphores)
Семафоры используются для контроля доступа к ограниченному количеству ресурсов. Вот пример использования семафора для ограничения количества одновременных подключений к базе данных:
import threading
import time
database_connections = 3
connection_semaphore = threading.Semaphore(database_connections)
def use_database():
with connection_semaphore:
print(f"{threading.current_thread().name} acquired a database connection.")
time.sleep(2) ## Simulating database operation
print(f"{threading.current_thread().name} released a database connection.")
threads = []
for _ in range(5):
t = threading.Thread(target=use_database)
threads.append(t)
t.start()
for t in threads:
t.join()
В этом примере connection_semaphore
используется для ограничения количества одновременных подключений к базе данных до 3. Каждый поток должен получить "разрешение" от семафора, прежде чем он сможет использовать подключение к базе данных.
Использование условных переменных (Condition Variables)
Условные переменные позволяют потокам ждать, пока определенные условия не будут выполнены, прежде чем продолжить выполнение. Вот пример использования условной переменной для координации производства и потребления элементов в очереди:
import threading
import time
queue = []
queue_size = 5
queue_condition = threading.Condition()
def producer():
with queue_condition:
while len(queue) == queue_size:
queue_condition.wait()
queue.append(1)
print(f"{threading.current_thread().name} produced an item. Queue size: {len(queue)}")
queue_condition.notify_all()
def consumer():
with queue_condition:
while not queue:
queue_condition.wait()
item = queue.pop(0)
print(f"{threading.current_thread().name} consumed an item. Queue size: {len(queue)}")
queue_condition.notify_all()
producer_threads = [threading.Thread(target=producer) for _ in range(2)]
consumer_threads = [threading.Thread(target=consumer) for _ in range(3)]
for t in producer_threads + consumer_threads:
t.start()
for t in producer_threads + consumer_threads:
t.join()
В этом примере переменная queue_condition
используется для координации производства и потребления элементов в очереди. Производители ждут, пока в очереди появится свободное место, а потребители ждут, пока в очереди появятся элементы.
Эти примеры демонстрируют, как можно использовать различные примитивы синхронизации, предоставляемые модулем threading
Python, для эффективного использования общих ресурсов и избежания распространенных проблем параллелизма.