«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Сравнение асинхронных моделей Python и ArkScript

Сравнение асинхронных моделей Python и ArkScript

Опубликовано 8 ноября 2024 г.
Просматривать:897

Comparing Python and ArkScript asynchronous models

В последнее время Python привлек много внимания. Выпуск 3.13, запланированный на октябрь этого года, положит начало огромной работе по удалению GIL. Предварительная версия уже доступна для любопытных пользователей, которые хотят попробовать Python (почти) без GIL.

Вся эта шумиха заставила меня заняться моим собственным языком, ArkScript, поскольку в прошлом у меня тоже была глобальная блокировка виртуальной машины (добавлена ​​в версии 3.0.12 в 2020 году, удалена в 3.1.3 в 2022 году), чтобы сравните вещи и заставьте меня глубже разобраться в том, как и почему используется Python GIL.

Определения

  1. Для начала давайте определим, что такое GIL (Глобальная блокировка интерпретатора):

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

Википедия — Глобальная блокировка переводчика

  1. Параллелизм — это когда две или более задач могут запускаться, выполняться и завершаться в перекрывающиеся периоды времени, но это не означает, что они обе будут выполняться одновременно.

  2. Параллелизм — это когда задачи выполняются буквально одновременно, например, на многоядерном процессоре.

Для более подробного объяснения ознакомьтесь с ответом на вопрос «Переполнение стека».

GIL Python

GIL может увеличить скорость однопоточных программ, поскольку вам не нужно устанавливать и снимать блокировки для всех структур данных: по умолчанию весь интерпретатор заблокирован, поэтому вы в безопасности.

]

Однако, поскольку на каждый интерпретатор существует один GIL, это ограничивает параллелизм: вам нужно создать совершенно новый интерпретатор в отдельном процессе (используя модуль многопроцессорности вместо многопоточности), чтобы использовать более одного ядра! Это обходится дороже, чем просто создание нового потока, потому что теперь вам придется беспокоиться о межпроцессном взаимодействии, что добавляет немало накладных расходов (см. GeekPython — GIL Become необязательно в Python 3.13 для тестов).

Как это влияет на асинхронность Python?

В случае Python это связано с тем, что основная реализация, CPython, не имеет потокобезопасного управления памятью. Без GIL следующий сценарий приведет к возникновению состояния гонки:

  1. создать общую переменную count = 5
  2. тема 1: count *= 2
  3. поток 2: count = 1

Если поток 1 запускается первым, счетчик будет равен 11 (count * 2 = 10, затем count 1 = 11).

Если поток 2 запускается первым, счетчик будет равен 12 (счет 1 = 6, затем счетчик * 2 = 12).

Порядок выполнения имеет значение, но может случиться и хуже: если оба потока прочитают count одновременно, один сотрет результат другого, и count будет либо 10, либо 6!

В целом, наличие GIL упрощает и ускоряет реализацию (CPython) в общих случаях:

  • быстрее в однопоточном случае (нет необходимости устанавливать/снимать блокировку для каждой операции)
  • быстрее в многопоточном случае для программ, связанных с вводом-выводом (поскольку они происходят за пределами GIL)
  • быстрее в многопоточном случае для программ, ориентированных на ЦП, которые выполняют свою ресурсоемкую работу на C (поскольку GIL высвобождается до вызова кода C)

Это также упрощает упаковку библиотек C, поскольку вам гарантирована потокобезопасность благодаря GIL.

Обратной стороной является то, что ваш код асинхронный, как в параллельный, но не параллельный.

[!ПРИМЕЧАНИЕ]
В Python 3.13 удаляется GIL!

В PEP 703 добавлена ​​конфигурация сборки --disable-gil, чтобы после установки Python 3.13 вы могли получить выгоду от повышения производительности в многопоточных программах.

Модель асинхронного/ожидания Python

В Python функции должны иметь цвет: они либо «нормальные», либо «асинхронные». Что это означает на практике?

>>> def foo(call_me):
...     print(call_me())
... 
>>> async def a_bar():
...     return 5
... 
>>> def bar():
...     return 6
... 
>>> foo(a_bar)
:2: RuntimeWarning: coroutine 'a_bar' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
>>> foo(bar)
6

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

Мы получаем иерархию функций, поскольку «обычные» функции необходимо сделать асинхронными, чтобы использовать ключевое слово await, необходимое для вызова асинхронных функций:

         can call
normal -----------> normal

         can call
async - -----------> normal
       |
       .-----------> async                    

Помимо доверия вызывающему объекту, нет способа узнать, является ли обратный вызов асинхронным или нет (если только вы не попытаетесь сначала вызвать его внутри блока try/Exception, чтобы проверить наличие исключения, но это некрасиво).

Параллелизм ArkScript

В начале ArkScript использовал глобальную блокировку виртуальной машины (сродни GIL в Python), поскольку модуль http.arkm (используемый для создания HTTP-серверов) был многопоточным и вызывал проблемы с виртуальной машиной ArkScript из-за изменения ее состояния путем изменения переменных. и вызов функций в нескольких потоках.

Затем, в 2021 году, я начал работать над новой моделью обработки состояния виртуальной машины, чтобы мы могли легко ее распараллеливать, и написал об этом статью. Позже он был реализован к концу 2021 года, и глобальная блокировка виртуальной машины была удалена.

ArkScript асинхронный/ожидающий

ArkScript не присваивает цвет асинхронным функциям, потому что они не существуют в языке: у вас есть либо функция, либо замыкание, и оба могут вызывать друг друга без какого-либо дополнительного синтаксиса (замыкание — это объект для бедняков, на этом языке: функция, хранящая изменяемое состояние).

Любую функцию можно сделать асинхронной на сайте вызова (вместо объявления):

(let foo (fun (a b c)
    (  a b c)))

(print (foo 1 2 3))  # 6

(let future (async foo 1 2 3))
(print future)          # UserType
(print (await future))  # 6
(print (await future))  # nil

Используя встроенную функцию async, мы создаем std::future под капотом (используя std::async и потоки) для запуска нашей функции с заданным набором аргументов. Затем мы можем вызвать await (еще одну встроенную функцию) и получить результат, когда захотим, что заблокирует текущий поток виртуальной машины до тех пор, пока функция не вернется.

Таким образом, можно ожидать от любой функции и от любого потока.

Особенности

Все это возможно, потому что у нас есть одна виртуальная машина, которая работает с состоянием, содержащимся внутри Ark::internal::ExecutionContext, привязанного к одному потоку. Виртуальная машина используется совместно потоками, а не контекстами!

        .---> thread 0, context 0
        |            ^
VM  thread 1, context 1              

При создании будущего с использованием асинхронности мы:

  1. копирование всех аргументов в новый контекст,
  2. создание совершенно нового стека и областей применения,
  3. наконец-то создайте отдельную тему.

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

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

В первом случае это замедлит ваш процесс (даже компьютер), поскольку ОС пытается выделить время каждому потоку; во втором случае это планировщик Python, который должен будет переключаться между всеми вашими сопрограммами.

[!ПРИМЕЧАНИЕ]
Изначально ArkScript не предоставляет механизмов синхронизации потоков, но даже если мы передаем UserType (который является оберткой поверх объектов C с удаленным типом объектов) в функцию, базовый объект не будет t скопировано.

При тщательном кодировании можно было бы создать блокировку с использованием конструкции UserType, которая позволила бы синхронизировать потоки.

(let lock (module:createLock))
(let foo (fun (lock i) {
  (lock true)
  (print (str:format "hello {}" i))
  (lock false) }))
(async foo lock 1)
(async foo lock 2)

Заключение

ArkScript и Python используют два совершенно разных типа async/await: первый требует использования async в месте вызова и порождает новый поток со своим собственным контекстом, а второй требует, чтобы программист помечал функции как асинхронные для иметь возможность использовать await, и эти асинхронные функции являются сопрограммами, работающими в том же потоке, что и интерпретатор.

Источники

  1. Stack Exchange — Почему Python был написан с использованием GIL?
  2. Python Wiki — GlobalInterpreterLock
  3. stuffwithstuff – Какого цвета ваша функция?

Исходно с сайта lexp.lt

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/lexplt/comparing-python-and-arkscript-asynchronous-models-3l60?1. Если есть какие-либо нарушения, свяжитесь с [email protected], чтобы удалить их.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3