Concurrency: процессы (process) и потоки (thread)

Источники

Зачем нужна concurrency

  • Эффективнее используем многоядерную архитектуру.
  • Разделяем логику программы на полностью или частично асинхронные секции (пингуем несколько серверов одновременно).

Синхронизация доступа к ресурсам

Если можно разбить задачу на независимые подзадачи БЕЗ синхронизации - сделайте это.

Проблема producer-consumer

Один (thread, process) producer производит числа от 0 до 9. Другой такой же объект - consumer - "потребляет" эти числа.

Если нет синхронизации, то могут возникнуть проблемы.

import threading

x = 0

def producer():
    global x
    for i in range(5):
        x = i
        print('producer', x)

def consumer():
    global x
    for i in range(5):
        print('consumer', x)


# init threads
t1 = threading.Thread(target=producer, args=())
t2 = threading.Thread(target=consumer, args=())

# start threads
t1.start()
t2.start()

# join threads to the main thread
t1.join()
t2.join()
print('Main', x)

Продьюсер может очень быстро производить числа, а консьюмер тупит.

producer 0
producer 1
producer 2
producer 3
producer 4
consumer 4
consumer 4
consumer 4
consumer 4
consumer 4
Main 4

Или наоборот, продьюсер еще не успел произвести число (потому что в него добавили в начало sleep(1), например), а консьюмер уже его хочет обработать.

consumer 0
consumer 0
consumer 0
consumer 0
consumer 0
producer 0
producer 1
producer 2
producer 3
producer 4
Main 4

Атомарность операций

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

Напишем для этого код:

id = 0

def get_id():
    global id
    ... do something with id ...
    id += 1

Если запустить в один тред, то все работает хорошо. Если запустить в несколько тредов, то получаем, например, дублирование номеров. Почему?

Операция id += 1 - НЕ атомарная.

Атомарной называют операцию, которая выполняется за 1 шаг без возможности ее прерывания.

id += 1 состоит из 3 операций: чтения id, вычисления числа на 1 больше, чем прочитанное и запись результата в id. В любой момент времени между операциями может произойти переключение на другой поток.

Атомарные операции:

  • чтение или изменение одного атрибута объекта
  • чтение или изменение одной глобальной переменной
  • выборка элемента из списка
  • модификация списка "на месте" (т.е. с помощью метода append)
  • выборка элемента из словаря
  • модификация словаря "на месте" (т.е. добавление элемента, или вызов метода clear)

Заметьте, или чтение, или изменение атрибута - атомарны, но чтение и последующее изменение атрибута - НЕ атомарная операция.

Критическая секция

участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устройству), который не должен быть одновременно использован более чем одним потоком (процессом) исполнения. При нахождении в критической секции двух (или более) процессов возникает состояние «гонки» («состязания»).

Атомарность операции не гарантирует, что она не является критической секцией. Очистка словаря - атомарная операция. Но если в это время этот же словарь кто-то перебирает в цикле, то очевидно, что и очистка, и перебор общего словаря - это критические секции.

Для корректной работы критических секций программы используют разные механизмы синхронизации.

results matching ""

    No results matching ""