Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GIL в Python: зачем он нужен и как с этим жить

GIL в Python: зачем он нужен и как с этим жить

Григорий Петров (Digital October)

В своем докладе Григорий проведет краткий экскурс в историю потоков и расскажет, зачем был создан GIL. Будут рассмотрены практические вопросы многопоточности в Python и способы работы с GIL.

Moscow Python Meetup
PRO

September 12, 2013
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. GIL в Python
    зачем он нужен и как с этим жить

    View Slide

  2. Что сейчас будет?
    Время 30-40 минут
    Ведущий Григорий Петров
    Специализация Руководство разработкой
    Опыт Более 15 лет
    Вопросы В конце доклада

    View Slide

  3. Warning
    В докладе много упрощений и допущений,
    особенно это касается многопроцессорных
    систем.
    Точность принесена в жертву простоте
    изложения, чтобы не превратить обзорный
    доклад с плюшками в сухую и бесполезную
    лекцию.

    View Slide

  4. Упрощение до предела
    У компьютера есть память и процессор. Все
    что компьютер делает, - это берет байтики из
    памяти, грустно на них смотрит и меняет на
    другие байтики в памяти.
    И все.

    View Slide

  5. Первые шаги
    Первые компьютеры и первые
    операционные системы были очень простые:
    они выполняли только одну программу за
    раз.
    Все программы загружались в общую память и
    передавали друг другу управление –
    кооперативная многозадачность.

    View Slide

  6. И первые хотелки
    Пользователи хотели выполнять несколько
    программ одновременно. И программисты
    создали абстракцию - процесс. С
    кооперативной многозадачностью.
    Все программы загружались в общую память
    и передавали друг другу управление.

    View Slide

  7. Но программисты не смогли
    Программисты не смогли писать программы без
    ошибок - слишком дорого. И когда программа
    зависала, она не передавала никому управление,
    и зависали все запущенные программы во главе с
    операционной системой.
    … А еще программы портили память.
    Надо было что-то делать.

    View Slide

  8. Вытесняющая многозадачность
    Раз в 20 миллисекунд процесс засыпает, и
    просыпается мафия операционная система,
    которая обозревает спящие процессы и решает,
    кому из них просыпаться. Зависшие процессы
    больше не останавливают операционную систему.
    Правильно написанная программа большую часть
    времени спит, ожидая чего-нибудь интересного.
    Ее разбудят.

    View Slide

  9. Виртуальная память
    В CPU была добавлена функция обмана
    программ. Каждая программа считает, что у
    нее есть вся память от 0 до бесконечности*.
    А при записи и чтении соответствующий
    кусочек памяти записывается и читается в
    физическую память.
    * Для 32-битных систем бесконечность - от 2 до 3 гигабайт, в зависимости от
    настроек.

    View Slide

  10. Но программисты снова не смогли
    В процессе написания больших и сложных
    программ возник ряд вопросов:

    Если программа читает файл, то она ждет, пока
    операционная система вернет управление.

    Асинхронные функции и алгоритмы слишком
    сложны и не всегда доступны.

    Запуск дополнительных процессов слишком
    долгий. *
    * Для Windows :)

    View Slide

  11. Поток

    Это процесс без виртуализации памяти :)

    Засыпает и просыпается так же, как раньше
    засыпали и просыпались процессы.

    Читает и пишет ту же виртуальную память,
    что и остальные потоки одного процесса.

    Быстро стартует.

    При создании процесса создается первый,
    он же “основной” поток.

    View Slide

  12. Но программисты снова не смогли
    Вам рассказывали, что проблема многопоточности
    - в одновременном доступе к памяти?
    Вас обманули. Компьютер ничего не делает
    “одновременно” - он играет в пошаговую
    стратегию. *

    Проблема потоков в том, что они засыпают и
    просыпаются в неожиданные моменты времени.
    * Многоядерные играют в несколько непересекающихся пошаговых стратегий.

    View Slide

  13. Совсем неожиданные
    Память Поток 2
    Поток 1
    Поток работает
    Поток работает
    Поток работает
    Поток спит
    Поток спит
    Поток спит
    Общая память потоков
    Общая память потоков
    Общая память потоков
    Указатель не 0?
    Читаем по адресу
    Обнулить :)
    Указатель → адрес
    Указатель → 0
    Указатель → 0

    View Slide

  14. И что тогда делает Lock в GIL?
    Если потоки и без GIL не выполняются
    одновременно, то как GIL может, цитируя
    документацию, “prevent multiple native threads
    from executing Python bytecodes at once
    (одновременно)”?

    View Slide

  15. И что тогда делает Lock в GIL?
    Если потоки и без GIL не выполняются
    одновременно, то как GIL может, цитируя
    документацию, “prevent multiple native threads
    from executing Python bytecodes at once
    (одновременно)”?
    А никак. Он не это делает.

    View Slide

  16. От чего защищает GIL?
    От того, что второй поток проснется
    неожиданно для первого и поменяет
    структуры в памяти интерпретатора.
    Проснувшийся первый поток будет очень
    удивлен. Вплоть до падения.

    View Slide

  17. Как он это делает?
    Тривиально. Программа не может предотвратить
    засыпание своих потоков, но она может
    контролировать, когда они проснутся.
    Python принудительно погружает все потоки, кроме
    текущего, в сон. Они спят и ждут светлого
    будущего.

    View Slide

  18. Как он это делает?
    Тривиально. Программа не может предотвратить
    засыпание своих потоков, но она может
    контролировать, когда они проснутся.
    Python принудительно погружает все потоки, кроме
    текущего, в сон. Они спят и ждут светлого будущего
    GIL.

    View Slide

  19. Как он это делает?
    Тривиально. Программа не может предотвратить
    засыпание своих потоков, но она может
    контролировать, когда они проснутся.
    Python принудительно погружает все потоки, кроме
    текущего, в сон. Они спят и ждут светлого будущего
    GIL.
    А когда Python понимает, что текущий поток уже
    слишком долго работает в гордом одиночестве, - он
    усыпляет его в ожидаемом месте и будит один из
    своих спящих потоков.

    View Slide

  20. И как ему это удается?
    Операционная система усыпляет и будит
    потоки неожиданно, и ей нет дела до того,
    что Python хочет свой текущий поток усыпить
    в ожидаемое время. Что же делать?

    View Slide

  21. И как ему это удается?
    Операционная система усыпляет и будит
    потоки неожиданно, и ей нет дела до того,
    что Python хочет свой текущий поток усыпить
    в ожидаемое время. Что же делать?
    Нужно интегрироваться в операционную
    систему и поменять логику, по которой она
    усыпляет и будит потоки!

    View Slide

  22. И как ему это удается?
    Операционная система усыпляет и будит
    потоки неожиданно, и ей нет дела до того,
    что Python хочет свой текущий поток усыпить
    в ожидаемое время. Что же делать?
    Нужно интегрироваться в операционную
    систему и поменять логику, по которой она
    усыпляет и будит потоки!
    Плохая идея.

    View Slide

  23. И как ему это удается?
    Операционная система усыпляет и будит
    потоки неожиданно, и ей нет дела до того,
    что Python хочет свой текущий поток усыпить
    в ожидаемое время. Что же делать?
    Нужно, чтобы текущий поток уснул сам. В
    ожидаемое время.

    View Slide

  24. Когда можно засыпать?

    Нельзя, чтобы другой поток проснулся, когда
    мы находимся внутри C-кода
    интерпретатора.

    Автор Python решил, что текущий поток
    будет засыпать раз в 100 тиков, где тик
    примерно соответствует одной инструкции
    интерпретатора.

    sys.getcheckinterval, sys.setcheckinterval

    View Slide

  25. Что такое тик?
    Это тик:
    a = 1

    View Slide

  26. Что такое тик?
    Это тик:
    a = 1
    К сожалению, это тоже тик:
    a in xrange( 10 ** 8 )

    View Slide

  27. Что такое тик?
    Это тик:
    a = 1
    К сожалению, это тоже тик:
    a in xrange( 10 ** 8 )
    100 таких “тиков” выполняются минуты

    View Slide

  28. Версия 3.2

    Потоки спят и ждут GIL не вечно, а 5 мс.

    Проснувшийся по таймауту поток обижается,
    выставляет флаг “хочу GIL” и снова засыпает.

    Текущий поток в конце каждого тика
    проверяет наличие флага “хочу GIL” и, если
    таковой есть, будит один из ожидающих GIL
    потоков и засыпает.

    sys.getswitchinterval, sys.setswitchinterval

    View Slide

  29. Итак, что же делает GIL?

    В нормальных условиях потоки засыпают и
    просыпаются по велению операционной
    системы - часто и неожиданно.

    GIL запрещает всем потокам Python, кроме
    текущего, просыпаться неожиданно.

    Текущий поток засыпает и просыпается по
    велению операционной системы. Но когда
    Python решает, что пора переключаться, -
    текущий поток засыпает сам.

    View Slide

  30. И где же здесь проблема?
    ?

    View Slide

  31. И где же здесь проблема?
    Мы запустили 10 потоков на скачивание
    файлов. Они не смогут просыпаться часто в
    случайный момент времени и будут качать с
    той же скоростью, как если бы были
    запущены последовательно?

    View Slide

  32. И где же здесь проблема?
    Мы запустили 10 потоков на скачивание
    файлов. Они не смогут просыпаться часто в
    случайный момент времени и будут качать с
    той же скоростью, как если бы были
    запущены последовательно?
    А вот и нет. В Python все предусмотрено.

    View Slide

  33. Механизм поднятия GIL
    GIL защищает от того, что поток A неожиданно
    уснет, поток B неожиданно проснется и поменяет
    байтики внутри интерпретатора, которые поток А
    ну никак не ожидает видеть измененными.
    Но если в потоке вызвана функция операционной
    системы для приема данных по сети - это не код
    python, и он не может поменять байтики в
    интерпретаторе.

    View Slide

  34. Механизм поднятия GIL
    Когда Python вызывает функцию
    операционной системы или внешней
    библиотеки, он отключает механизм GIL.

    View Slide

  35. Механизм поднятия GIL
    Когда Python вызывает функцию
    операционной системы или внешней
    библиотеки, он отключает механизм GIL.
    А после того как функция вернет
    управление, снова включает его. Это
    называется “поднять GIL” и “опустить GIL”.

    View Slide

  36. Промежуточный вывод
    Если у нас одноядерный процессор, GIL
    ничем не мешает нашим потокам и приносит
    пользу:

    Код, не имеющий отношения к
    интерпретатору, засыпает и просыпается,
    когда ему надо, и не ждет ничего лишнего.

    Python-код засыпает и просыпается
    аккуратно, чтобы ничего не повредилось.

    View Slide

  37. Физическая и логическая защита
    GIL защищает память от повреждений и
    делает тики атомарными. Но тики — довольно
    мелкая единица языка:
    if a > 0:
    a = 10
    Здесь от двух тиков и более.

    View Slide

  38. Логическая защита кода
    threading.Lock
    threading.Semaphore
    threading.Event
    threading.Queue

    View Slide

  39. А если много ядер?

    Если у вас много ядер и вы хотите
    максимизировать скорость выполнения
    именно Python кода — значит, у вас очень
    редкий случай :)

    NumPy и SciPy поднимают GIL на долгих
    операциях (работа с матрицами).

    Используйте библиотеки для расчетов

    Используйте процессы.

    View Slide

  40. Можно кидать помидоры
    Рассказывал, показывал и махал руками
    Григорий Петров.
    Со мной можно связаться:
    [email protected]
    www.facebook.com/grigoryvp
    Вопросы?

    View Slide