В: GIL когда-либо разблокирован для работы без IO-привязки? ... да, может (несколькими способами)
Это был вопрос O / P, верно, не так ли?
Итак, давайте разберемся с этим - python - интерпретируемый язык. Интерпретатор Python, по своей конструкции, использует GIL он же G lobal I nterpreter L ock (т. Е. Это его python- «LOCK-устройство» только для внутреннего использования и не имеет ничего общего с другими (O / S-блокировки, IO-блокировки и др.).
GIL-блокировка - это инструмент мягкой сигнализации, который используется внутри интерпретатора python для координации его собственной работы и, главным образом, для предотвращения любых конфликтов, вызванных параллелизмом (чтобы избежать двух попыток записать значение в некоторую переменную или избегать попытки прочитать, потенциально «старое» значение из переменной, которая «в настоящее время» записывает «новое» значение), таким образом, своего рода искусственно вводя детерминированный, чисто последовательный, в основном никогда не конфликтующий, порядок такого внутренние операции с питоном.
Это означает, что все потоки Python будут подчиняться сигнализации на основе GIL, и поэтому для любого пула потоков, координируемых с Python-GIL, установлено равное 1. Таким образом, за исключением случаев, когда ожидания, связанные с IO, вводят «естественное» (внешнее устройство) возникла необходимость для а) состояния ожидания (когда такой «естественно» ожидающий поток будет сигнализировать с помощью освобожденного GIL-замка питона, чтобы «одолжить» время ожидания такого потока, а не другому потоку Python, чтобы сделать что-то полезное, та же логика для ресурсоемкой обработки потоков не имеет смысла, так как ни один из потоков Python внутри такого вычислительного пула не имеет каких-либо «внешне» введенных «естественных» состояний ожидания, но нуждается в совершенно противоположном - столько же запланированного процессора - время, насколько это возможно ... но проклятый GIL играет циклический перебор pure - [SERIAL]
последовательность процессора, работающего с потоками Python один за другим: tA-tB-tC-tD-tE-...-tA-tB-tC-tD-tE-...
таким образом эффективно избегая любых и всех потенциальных [CONCURRENT]
преимуществ планирования процессов.
" Почему следующий пример кода с привязкой к ЦП работает параллельно и никогда не блокируется? "
Ну, все же все выполнялось как "чистая" - [SERIAL]
последовательность небольших промежутков времени, в течение которых ЦП работает над одним и единственным потоком Python, внутренне нарушаемым после каждой GIL-блокировки длительность релиза была потрачена, поэтому в результате получается, что вся работа «квази» одновременно обрабатывается (но все же это последовательность выполнения фактической работы, которая была суперсэмплирована в небольшие кванты времени выполнения и выполнялась одна за другой). еще один, пока работа не была закончена).
Итак, потоки Python фактически оплачивают много накладных расходов (чтение, перечитывание, иногда POSACK собирает, а затем принудительно выпускает программное обеспечение Python GIL-lock ), Это потребует значительных накладных расходов на производительность, но вы ничего не получите взамен всех этих многопоточных обработок. Ничего, но хуже производительность (Q.E.D. выше в @ galaxyan результаты испытаний)
Вы бы почувствовали это самостоятельно, если бы не вызывали простое fib(32)
, но некоторые более сложные вычисления хотели бы вычислить что-то более требовательное:
( len( str( [ np.math.factorial( 2**f ) for f in range( 20 ) ][-1] ) ) )
(кстати, обратите внимание, что fib()
не может быть способом продвинуться сюда, поскольку его рекурсивная формулировка скоро на чем-то вроде fib( 10**N )
начнет падать сразу после вашего N
превышает предел порога конфигурации интерпретатора python, установленный для максимального предела глубины рекурсии python ...
def aCrashTestDemoWORKER( id, aMaxNUMBER ):
MASKs = "INF: {2:} tid[{0:2d}]:: fib(2:{1:}) processing start..."
MASKe = "INF: {2:} tid[{0:2d}]:: fib(2:{1:}) processing ended...{3:}"
safeM = 10**max( 2, aMaxNUMBER )
pass; print( MASKs.format( id, safeM, datetime.datetime.utcnow() ) )
len( [ fib( someN ) for someN in range( safeM ) ] )
pass; print( MASKe.format( id, safeM, datetime.datetime.utcnow(), 20*"_" ) )
Q: GIL когда-либо разблокирован для работы без IO?
Да, это может быть - некоторая работа может быть выполнена, действительно GIL
- бесплатно.
Один из них, который сложнее организовать, - это скорее использовать multiprocessing
с бэкендом на основе подпроцесса - это позволяет избежать GIL-блокировки, но вы платите весьма приличную цену, выделяя столько полных копийвсе состояние сеанса Python (интерпретатор + все импортированные модули + все внутренние структуры данных, необходимы ли они для таких распределенных вычислений или нет), а также ваши (теперь INTER-PROCESS) коммуникации выполняют сериализацию / десериализацию до / после отправки даже одного бита информациитуда или обратно (это больно).Для получения подробных сведений о фактических «экономичных» издержках можно прочитать переформулировку закона Амдала, отражающую влияние как этих накладных расходов, так и продолжительности атомной обработки..
Другой случай, когда используется numba.jit()
скомпилированный или предварительно скомпилированный код, где LLVM-компилятор на основе smart numba
может получить инструкции вДекоратор с подписью (ями) вызова и другими деталями для работы в режиме nogil = true
, чтобы генерировать код, который не должен использовать (дорогостоящую) GIL-сигнализацию, где уместно запросить такойутешение.
Последний случай - перейти к гетерогенному распределенному вычислительному дизайну, где python остается координатором, а удаленные распределенные вычислительные блоки - это не содержащие GIL средства вычисления чисел, где внутренняя GIL-логика python не имеет смысла и являетсяпо дизайну игнорируется.
БОНУСНАЯ ЧАСТЬ:
Для получения дополнительной информации об уловках, требующих высокой производительности, вам может понравиться ( этот пост нанакладные расходы потоков мониторинга )