Дни одноядерных процессоров давно прошли.
В настоящее время, независимо от того, покупаете ли вы готовый ноутбук или высококлассный сервер для своего бизнеса, ваш процессор определенно будет иметь несколько ядер.
И иногда вашей программе нужно использовать преимущества этих нескольких ядер для параллельной работы.
Это может привести к увеличению пропускной способности, повышению производительности и повышению скорости отклика.
Но позвольте нам прояснить одну вещь.
Если высокая производительность и увеличенная пропускная способность абсолютно необходимы, Python не будет лучшим языком для поддержки параллельного программирования.
В этой ситуации мы лично предпочли бы вместо его golang (или старый добрый C ).
Но так как это статья о Python, давайте сосредоточимся на Python.
Перед тем, как погрузиться и написать свою первую параллельную программу, есть несколько концепций параллельной обработки, которые вы должны изучить в первую очередь.
Вот некоторые из этих концепций.
Если у вас есть данные, которые совместно используются несколькими потоками или процессами, важно синхронизировать доступ к этим общим ресурсам.
Если вы этого не сделаете, может возникнуть состояние гонки, которое может привести к неожиданным, а иногда и катастрофическим последствиям. Мы расскажем больше об условиях гонки позже.
Взаимное исключение означает, что один поток блокирует дальнейший прогресс других параллельных потоков, которые требуют использования общего ресурса.
Замки являются одной из различных реализаций взаимного исключения.
Чтобы понять, что такое замки, вы можете подумать о них с концептуальной точки зрения.
Если поток хочет получить доступ к общему ресурсу, этот поток должен захватить блокировку, прежде чем ему будет предоставлен доступ к этому ресурсу.
И после того, как это сделано с ресурсом, он снимает эту блокировку.
Если блокировка недоступна, потому что она захвачена другим потоком, то поток должен ждать, пока блокировка будет снята первой.
Эта простая концепция гарантирует, что не более одного потока может иметь доступ к общему ресурсу одновременно.
Взаимная блокировка – это когда ваша программа полностью останавливается, потому что некоторые потоки не могут развиваться дальше, потому что они не могут получить блокировку.
Например, представьте, что поток A ожидает в потоке B снятие блокировки. В то же время поток B ожидает в потоке A освобождение еще одной блокировки, которую в данный момент удерживает поток A.
В этой тяжелой ситуации ни поток A, ни поток B не могут развиваться дальше, поэтому ваша программа закрыта!
Вот что такое тупик.
И это случается чаще, чем вы думаете.
Что еще хуже, это также одна из самых сложных проблем для отладки.
Как мы упоминали ранее, состояние гонки – это ситуация, которая возникает, когда доступ к общему ресурсу не защищен (например, блокировками).
Это может привести к катастрофическим неожиданным результатам.
Посмотрите на этот пример.
import threading # x это общее значение x = 0 COUNT = 1000000 def inc(): global x for _ in range(COUNT): x += 1 def dec(): global x for _ in range(COUNT): x -= 1 t1 = threading.Thread(target=inc) t2 = threading.Thread(target=dec) t1.start() t2.start() t1.join() t2.join() print(x)
Вот что делает код выше:
Существует общая глобальная переменная x, которая инициализируется в 0.
Две функции inc и dec работают параллельно. inc () увеличивает значение x 1 миллион раз, тогда как dec () уменьшает значение x 1 миллион раз.
Быстро пройдясь по коду, можно сделать вывод, что конечное значение x должно быть 0 … но так ли это?
Вот что мы получаем, когда запускаем приведенный выше код.
$ python3 race.py 158120 $ python3 race.py 137791 $ python3 race.py -150265 $ python3 race.py 715644
Причина, по которой это происходит, заключается в том, что общий ресурс x не защищен (например, блокировками).
Только после того, как вы освоитесь с концепциями, рассмотренными выше, вы готовы научиться писать параллельные программы на Python.
Во-первых, вы должны узнать, как определение многопроцессорности в Python отличается от многопоточности. (Кстати, это совершенно не связано с потоками и процессами с точки зрения ОС ).
Чтобы понять это различие между многопроцессорностью и многопоточностью с точки зрения Python, вам необходимо изучить и понять глобальную блокировку интерпретатора (GIL).
Вам также необходимо узнать о потоках, очереди и многопроцессорных модулях Python.
Все эти модули предоставляют вам примитивы, необходимые для написания параллельных программ.
Начало:
Продолжение
Продолжение следует…