GIL когда-либо разблокирован для работы без IO? - PullRequest
0 голосов
/ 08 июля 2019

Известно, что GIL Python может быть временно отменен, что позволяет другим потокам выполнять код, связанный с IO. Многие встроенные функции, связанные с IO, поддерживают это.

Почему следующий пример кода с привязкой к процессору работает параллельно и никогда не блокируется?

def fib(n):
    return n if n < 2 else fib(n - 2) + fib(n - 1)


def worker(id, n):
    fib(n)
    print(f'I am worker {id}, computed the fib of n={n}.')


for i in range(5):
    t = threading.Thread(target=worker, args=(i, 32))    # run fib(32)
    t.start()

print('All threads are ready to start!')
print('Main computing fib too!', fib(34))

Ничего не блокирует, и print('All threads are ready to start!') выполняется первым. Все рабочие печатают результаты действительно неэффективного вычисления Фибоначчи почти одновременно. Они заканчивают до того, как main завершит свой собственный длинный вызов fib.

В этом коде нет работы, связанной с вводом-выводом, почему кажется, что все потоки могут работать одновременно с основным потоком?

Ответы [ 2 ]

0 голосов
/ 08 июля 2019

В: 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 не имеет смысла и являетсяпо дизайну игнорируется.

БОНУСНАЯ ЧАСТЬ:

Для получения дополнительной информации об уловках, требующих высокой производительности, вам может понравиться ( этот пост нанакладные расходы потоков мониторинга )

0 голосов
/ 08 июля 2019

это замедляется из-за gil (переключение контекста os).это медленнее, чем один поток

на моей машине:

def fib(n):
    return n if n < 2 else fib(n - 2) + fib(n - 1)
ts = time.time()
for _ in range(5):
    fib(32)
fib(34)
time.time() - ts

4.9730000495910645

your code

13.2970001698

import multiprocessing as mp
import time


def fib(n):
    return n if n < 2 else fib(n - 2) + fib(n - 1)


def worker(idn, n):
    fib(n)
    print 'I am worker {}, computed the fib of n={}.'.format(idn, n)

ts = time.time()
a = []
for i in range(5):
    t = mp.Process(target=worker, args=(i, 32))    # run fib(32)
    t.start()

[i.join() for i in a]
print('All processes are ready to start!')
print('Main computing fib too!', fib(34))

print time.time() - ts

1.91096901894

...