11.1 Global Interpreter Lock
Global Interpreter Lock (GIL)
— це механізм в
інтерпретаторі CPython, який забезпечує, що тільки один потік
виконує байт-код Python в будь-який момент часу. GIL
запобігає
паралельному виконанню потоків, що може негативно впливати на
продуктивність багатопотокових програм, особливо на багатоядерних
процесорах.
Причини існування GIL
Спрощення управління пам'яттю: GIL
спрощує
управління пам'яттю і збірку сміття, роблячи Python простішим для
реалізації.
Потокобезпечність: GIL
запобігає станам
гонок, роблячи виконання коду потокобезпечним без необхідності
використовувати блокування.
Проблеми, викликані GIL
Обмежена продуктивність: Багатопотокові
програми, які виконують обчислювально інтенсивні задачі, не можуть
повністю використати переваги багатоядерних процесорів через
обмеження GIL
.
Викривлення продуктивності: Програми, які активно використовують потоки для виконання задач, можуть стикатися з погіршенням продуктивності через перемикання контексту між потоками.
Зараз існує 4 основних способи «обходу GIL»:
11.2 Використання багатопроцесорності (multiprocessing)
Модуль multiprocessing
дозволяє створювати
процеси, які виконуються паралельно і не обмежені GIL
, так
як кожен процес має свій власний інтерпретатор Python і
пам'ять.
Приклад:
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 Асинхронне програмування
Асинхронне програмування з використанням
asyncio
дозволяє виконувати задачі паралельно, не
блокуючи основний потік. Хоча це не обходить GIL
в прямому сенсі,
воно дозволяє ефективно використовувати час очікування для виконання
інших задач.
Приклад:
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 Використання бібліотек з власним управлінням потоками
Деякі бібліотеки, такі як NumPy і SciPy, написані на C і використовують власні механізми управління потоками, що дозволяє їм обходити GIL і ефективно використовувати багатоядерні процесори для обчислень.
Власне, це і є одна з основних причин успіху Python, яким би повільним він не був. Всі складні обчислення переписані на мовах C/C++ і виконуються на всіх ядрах процесора або навіть одразу на ядрах відеокарти. А на сучасних відеокартах тисячі ядер.
Тому вже не важливо, наскільки мова швидка або повільна, якщо всі ресурсомісткі обчислення виконуються зовнішніми бібліотеками або взагалі винесені в віддалені дата-центри.
11.5 Виконання обчислень поза інтерпретатором Python
Використання розширень на C/C++ або інших мовах, які можуть виконувати обчислення поза інтерпретатором Python і потім повертати результат. Це дозволяє уникнути блокування GIL на час виконання інтенсивних обчислень.
Приклад використання Cython:
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
Компіляція і використання:
cythonize -i example.pyx
import example
print(example.compute(1000000))