Напомню вам ваш комментарий на который я ответил: «раст делает больно программисту, и ничего за это не дает». Какие конкретно гарантии раст дает, а какие нет - вы можете почитать в официальной документации, в раст-буке, номиконе и т.д., а не спорить об определениях с Википедии
Раз уж вы ссылаетесь на Википедию, то там написано следующее: «Безопасность доступа к памяти — концепция в разработке программного обеспечения, целью которой является избежание программных ошибок, которые ведут к уязвимостям, связанным с доступом к оперативной памяти компьютера, таким как переполнения буфера и висячие указатели»
Где, простите, вы вычитали, что безопасность доступа к памяти - это отсутствие утечек?
Запомните уже наконец: memory safety - это не про гарантии отсутствия «утечек», это про гарантии отсутствия UB при некорректной работе с той самой памятью, которое легко допустимо в С/C++, например. И (safe) rust дает эти самые гарантии.
я спорил не с вашим возражением в целом, а конкретно с вашим выражением про "запуск задач" и пытался донести, что "запустить задачу" явно + при этом не блокируясь в некоторых случаях попросту нельзя
А если в целом, то будет ваш код работать реально асинхронно (a.k.a. конкурентно) или же это будет последовательный код с async/await - зависит от того, как вы его напишите
P.S. если по-честному, то данная статья вообще не стоит обсуждения ))
Скорее, это вы прицепились к моей формулировке "питонячего потока" ) Надеюсь, я выше в комментариях смог донести что именно имелось в виду.
Касательно CPU-bound задач - к сожалению, не для всего есть готовые либы, использующие "обходные пути" (отпускание GIL и т.д.). А писать свои реализации на языках без GIL / реализации, использующие Python C API - то еще удовольствие
где я говорил что "код с использованием asyncio последовательный"? Я говорил про то, что код с использованием asyncio может работать синхронно, если он написан без знаний/понимания нюансов того, как работает ивент-луп. И шансы стать на эти "грабли" крайне велики, особенно для тех разработчиков, кто не сильно посвящен в детали. Причина же данной проблемы лежит в плоскости того, как именно реализован ивент-луп. Да и, в целом, асинхронная модель в пайтоне оставляет желать лучшего.
Еще раз: любой поток созданный в пайтон-коде будет использовать GIL. Любой поток, созданный библиотекой на С/C++/Rust, НЕ будет использовать GIL. Это попросту разные уровни абстракции. Что конкретно вам тут не понятно? Также выше я подробно расписал, почему логическое разделение на "потоки языка X" и "потоки языка Y" вполне уместны и валидны в контексте пайтона. Подчеркиваю - ЛОГИЧЕСКОЕ, а не ФИЗИЧЕСКОЕ.
Разберитесь в вопросе для начала, чтобы понимать то, о чем вам пишут. А то уже сотый коммент просто выдаете общеизвестные истины, параллельно повторяя как мантру "нет никаких других потоков, есть только потоки ОС")
Но наличие кооперативности не делает конкуретный код внезапно последовательным или синхронным.
чтобы код был конкурентным (т.е. выполнялся конкуретно), он должен быть написан со знанием нюансов работы ивент-лупа. Наличие async/await в коде не делает его конкуретным априори, и у питонячего ивент-лупа есть конкретные ограничения. Об этом и речь.
К чему вы тут виды многозадачности привели - вообще не понятно. Некоторые языки (например, Rust) релизуют асинк-модель (например, tokio runtime) с использованием тредов, но при этом многозадачность там - кооперативная.
оттуда же, откуда они берутся в любой библиотеке написанной на C и использующей многопоточность.
Внезапно, если в потоке порожденным питоном отпустить GIL (а это делается в куче случаев, начиная с банального hashlib), то гил отпустится и можно занять несколько ядер
спасибо за очередную порцию общеизвестных вещей) Интересно, вы сами себя пытаетесь удивить своими же познаниями?)
Вы можете написать свою собственную числодробилку на С/С++/Rust и юзать в ее реализации N потоков, а потом заиспользовать это в пайтоне. Все будет работать параллельно, потому что потоки уровня низлежащего языка не блокируются GIL (потому что они тупо не знают вообще что такое GIL).
Повторюсь: интерпретатор пайтона (CPython) использует свою абстракцию над потоками ОС (в комменте выше я писал какую - PyThreadState). Благодаря этому реализуется синхронизация потоков через GIL. «Физически» же выполнение пайтон байт-кода происходит в ОС потоке (иного я нигде и не утверждал). Так что «питонячий поток» - это ОС поток + PyThreadState, который использует интерпретатор. Потоки же, порождаемые С-шными либами - это тоже OC потоки, но они могут выполняться реально параллельно (на разных ядрах CPU), т.к. они вообще ничего не знают про GIL. Исходя из вышесказанного, вполне валидно логически разделять потоки на "питонячие" и (например) "с-шные", несмотря на то, что под капотом и там, и там - ОС потоки
P.S. Советую почитать очень занимательную статью за авторством Виктора Скворцова про GIL (она на английском)
создание таски из корутины (если вы про asyncio.create_task(coro)) не является само по себе "триггером для запуска", так как это а) создание объекта Таски б) добавление созданной Таски в очередь ивент-лупа.
Когда именно таска будет запущена - она будет запущена на следующей же итерации ивент-лупа. Чтобы переключить итерацию, вы либо явно await'ите вашу таску (что приводит к запуску ее на ивент-лупе, но и к ожиданию ее завершения), либо же вы переключаете итерацию ивент-лупа через `await asyncio.sleep(0)` (что с точки зрения эргономики языка - дичь полнейшая)
Нет никаких "мыслей планировщика" - есть конкретный алгоритм, по которому работает ивент-луп. Да и отдельного "планировщика" в питонячем asyncio тоже, по факту, нет - там все сделано " в лоб", если сравнивать с другими ЯП.
GIL - это не «обычный мьютекс», а структура, состоящая из набора мьютексов и кондишен-переменных. И взаимодействие с ней происходит по определенному (достаточно хитрому) алгоритму.
про «альтернативную реализацию потоков в пайтоне» я нигде и не писал
Такое чувство, что вы на ходу гуглите и кидаетесь определениями, которые и так всем известны и с которыми никто и не спорил)
неправда, если взять CPU-bound задачи, с высокой вероятностью вы будете использовать для этого нативные библиотеки, которые отпускают GIL, потоки будут всё ещё эффективны
Только это будут не питонячие потоки (например, numpy)
А что Вы понимаете под "запусканием новых тасков"? Если Вы внутри цикла будете создавать новую таску на каждой итерации и awaitить ее там же (собсно, await приводит к запуску таски), то весь ваш цикл будет полностью синхронным, потому что await будет блокировать каждую итерацию цикла, дожидаясь завершения таски в рамках этой итерации.
Для того, чтобы этого избежать, можно а) создать таску, б) "в ручную" переключить event-loop на следующую его итерацию. Но если забыть сделать последнее (и при условии что внутри вашего цикла нет других операций, переключающихся на ивент-луп), то все ваши таски просто будут лежать мертвым грузом внутри ready очереди ивент-лупа в статусе Pending.
Другая проблема тут - обработка ошибок в тасках. Если мы НЕ awaitим нашу таску явно, то у нас и нет контроля над ее выполнением, следовательно, обработать ошибку (если она случилась внутри таски) явным способом (через try/ except) мы не можем (тут спасают TaskGroupы)
Имхо, в этом заключается самый большой косяк реализации асинхронности в питончике:
без дополнительных "приблуд" типа gather, wait или TaskGroup вы не получите желаемой асинхронности в принципе - у вас будет синхронный код с async / await
операции типа asyncio.create_task / TaskGroup().create_task лишь создают таску и добавляют ее в очередь выполнения, но не запускают ее
Напомню вам ваш комментарий на который я ответил: «раст делает больно программисту, и ничего за это не дает». Какие конкретно гарантии раст дает, а какие нет - вы можете почитать в официальной документации, в раст-буке, номиконе и т.д., а не спорить об определениях с Википедии
Раз уж вы ссылаетесь на Википедию, то там написано следующее: «Безопасность доступа к памяти — концепция в разработке программного обеспечения, целью которой является избежание программных ошибок, которые ведут к уязвимостям, связанным с доступом к оперативной памяти компьютера, таким как переполнения буфера и висячие указатели»
Где, простите, вы вычитали, что безопасность доступа к памяти - это отсутствие утечек?
Говорить про «утечки и краши» языка, и привести как аргумент ссылку на форум, где обсуждается «unwrap() hurts readability» …
Запомните уже наконец: memory safety - это не про гарантии отсутствия «утечек», это про гарантии отсутствия UB при некорректной работе с той самой памятью, которое легко допустимо в С/C++, например. И (safe) rust дает эти самые гарантии.
я спорил не с вашим возражением в целом, а конкретно с вашим выражением про "запуск задач" и пытался донести, что "запустить задачу" явно + при этом не блокируясь в некоторых случаях попросту нельзя
А если в целом, то будет ваш код работать реально асинхронно (a.k.a. конкурентно) или же это будет последовательный код с async/await - зависит от того, как вы его напишите
P.S. если по-честному, то данная статья вообще не стоит обсуждения ))
Скорее, это вы прицепились к моей формулировке "питонячего потока" ) Надеюсь, я выше в комментариях смог донести что именно имелось в виду.
Касательно CPU-bound задач - к сожалению, не для всего есть готовые либы, использующие "обходные пути" (отпускание GIL и т.д.). А писать свои реализации на языках без GIL / реализации, использующие Python C API - то еще удовольствие
где я говорил что "код с использованием asyncio последовательный"? Я говорил про то, что код с использованием asyncio может работать синхронно, если он написан без знаний/понимания нюансов того, как работает ивент-луп. И шансы стать на эти "грабли" крайне велики, особенно для тех разработчиков, кто не сильно посвящен в детали. Причина же данной проблемы лежит в плоскости того, как именно реализован ивент-луп. Да и, в целом, асинхронная модель в пайтоне оставляет желать лучшего.
Еще раз: любой поток созданный в пайтон-коде будет использовать GIL. Любой поток, созданный библиотекой на С/C++/Rust, НЕ будет использовать GIL. Это попросту разные уровни абстракции. Что конкретно вам тут не понятно?
Также выше я подробно расписал, почему логическое разделение на "потоки языка X" и "потоки языка Y" вполне уместны и валидны в контексте пайтона. Подчеркиваю - ЛОГИЧЕСКОЕ, а не ФИЗИЧЕСКОЕ.
Разберитесь в вопросе для начала, чтобы понимать то, о чем вам пишут. А то уже сотый коммент просто выдаете общеизвестные истины, параллельно повторяя как мантру "нет никаких других потоков, есть только потоки ОС")
чтобы код был конкурентным (т.е. выполнялся конкуретно), он должен быть написан со знанием нюансов работы ивент-лупа. Наличие async/await в коде не делает его конкуретным априори, и у питонячего ивент-лупа есть конкретные ограничения. Об этом и речь.
К чему вы тут виды многозадачности привели - вообще не понятно. Некоторые языки (например, Rust) релизуют асинк-модель (например, tokio runtime) с использованием тредов, но при этом многозадачность там - кооперативная.
оттуда же, откуда они берутся в любой библиотеке написанной на C и использующей многопоточность.
спасибо за очередную порцию общеизвестных вещей) Интересно, вы сами себя пытаетесь удивить своими же познаниями?)
Вы можете написать свою собственную числодробилку на С/С++/Rust и юзать в ее реализации N потоков, а потом заиспользовать это в пайтоне. Все будет работать параллельно, потому что потоки уровня низлежащего языка не блокируются GIL (потому что они тупо не знают вообще что такое GIL).
Повторюсь: интерпретатор пайтона (CPython) использует свою абстракцию над потоками ОС (в комменте выше я писал какую - PyThreadState). Благодаря этому реализуется синхронизация потоков через GIL. «Физически» же выполнение пайтон байт-кода происходит в ОС потоке (иного я нигде и не утверждал). Так что «питонячий поток» - это ОС поток + PyThreadState, который использует интерпретатор. Потоки же, порождаемые С-шными либами - это тоже OC потоки, но они могут выполняться реально параллельно (на разных ядрах CPU), т.к. они вообще ничего не знают про GIL. Исходя из вышесказанного, вполне валидно логически разделять потоки на "питонячие" и (например) "с-шные", несмотря на то, что под капотом и там, и там - ОС потоки
P.S. Советую почитать очень занимательную статью за авторством Виктора Скворцова про GIL (она на английском)
создание таски из корутины (если вы про
asyncio.create_task(coro)
) не является само по себе "триггером для запуска", так как это а) создание объекта Таски б) добавление созданной Таски в очередь ивент-лупа.Когда именно таска будет запущена - она будет запущена на следующей же итерации ивент-лупа. Чтобы переключить итерацию, вы либо явно await'ите вашу таску (что приводит к запуску ее на ивент-лупе, но и к ожиданию ее завершения), либо же вы переключаете итерацию ивент-лупа через `await asyncio.sleep(0)` (что с точки зрения эргономики языка - дичь полнейшая)
Нет никаких "мыслей планировщика" - есть конкретный алгоритм, по которому работает ивент-луп. Да и отдельного "планировщика" в питонячем asyncio тоже, по факту, нет - там все сделано " в лоб", если сравнивать с другими ЯП.
так что является триггером для запуска таски/корутины?
GIL - это не «обычный мьютекс», а структура, состоящая из набора мьютексов и кондишен-переменных. И взаимодействие с ней происходит по определенному (достаточно хитрому) алгоритму.
про «альтернативную реализацию потоков в пайтоне» я нигде и не писал
Такое чувство, что вы на ходу гуглите и кидаетесь определениями, которые и так всем известны и с которыми никто и не спорил)
И сейчас мы возвращаемся в начальную точку: как запустить корутину, не дожидаясь ее результата?
Чего именно нет? Абстракций интерпретатора поверх OS тредов? Как, по-вашему, интерпретатор синхронизирует выполнение потоков через GIL?
Что-то не совсем понятно, с чем конкретно вы спорите )
Верно, только поток запускается методом start(), а у таски попросту нет аналогичного метода
Питоновский тред == тред, выполняющий питонячий байт-код и описываемый структурой PyThreadState. То, что там под капотом OS тред я в курсе ))
Только это будут не питонячие потоки (например,
numpy
)А что Вы понимаете под "запусканием новых тасков"? Если Вы внутри цикла будете создавать новую таску на каждой итерации и
await
ить ее там же (собсно,await
приводит к запуску таски), то весь ваш цикл будет полностью синхронным, потому чтоawait
будет блокировать каждую итерацию цикла, дожидаясь завершения таски в рамках этой итерации.Для того, чтобы этого избежать, можно а) создать таску, б) "в ручную" переключить event-loop на следующую его итерацию. Но если забыть сделать последнее (и при условии что внутри вашего цикла нет других операций, переключающихся на ивент-луп), то все ваши таски просто будут лежать мертвым грузом внутри
ready
очереди ивент-лупа в статусе Pending.Другая проблема тут - обработка ошибок в тасках. Если мы НЕ
await
им нашу таску явно, то у нас и нет контроля над ее выполнением, следовательно, обработать ошибку (если она случилась внутри таски) явным способом (черезtry
/except
) мы не можем (тут спасаютTaskGroup
ы)Имхо, в этом заключается самый большой косяк реализации асинхронности в питончике:
без дополнительных "приблуд" типа
gather
,wait
илиTaskGroup
вы не получите желаемой асинхронности в принципе - у вас будет синхронный код сasync / await
операции типа
asyncio.create_task
/TaskGroup().create_task
лишь создают таску и добавляют ее в очередь выполнения, но не запускают ее