Если что такое параллелизм более‑менее все разработчики понимают, то объяснение асинхронности через аналогии с кассирами/поварами не ложно, но, как мне кажется, вредно, так как вводит в очень большое заблуждение.
В данной статье я разберу эту проблему на примерах Python и Go и попробую дать свою правильную аналогию.

TL;DR

Асинхронность и многопоточка решают одну и ту же задачу для IO‑bound операций — конкурентность, — но отличаются только синтаксисом, экосистемой и производительностью.

Для IO-bound!

Для CPU‑bound кода выгоды в асинхронности нет, параллелизм достигается на потоках.

Вопрос?

Как‑то раз в интернете я наткнулся на опросник, который используют HR при первом контакте с кандидатом на позицию гофера.

Скрытый текст

Очень надеюсь, что этот опросник — фейк и его никто никогда не использовал

Один из вопросов в нем звучал так:

«Если к функциям дописать ключевое слово go, они будут работать асинхронно?»

Для не гоферов

Ключевое слово go вызывает функцию в отдельной горутине, очень грубо говоря, как питоновское asyncio.create_task

Странная формулировка, подумал я, но чисто технически ответ же — «да»? Может вопрос с подвохом и горутину надо заджойнить (добавить sync.WaitGroup)?
Или вопрос про GOMAXPROCS? Если разрешить использовать только 1 OS поток, то горутина, конечно, будет работать асинхронно, но если 2 — может и параллельно.
Или вопрос про то, что будет с горутинами, что еще не завершили работу, но основная горутина (main) завершилась?

Оказывается, ответ таков:

«во‑первых нет, в Go нет асинхронности, во‑вторых нужна синхронизация»

Отвратительность этого ответа (и вопроса) я даже не хочу обсуждать, но давайте остановимся на асинхронности. Разве ее нет в Go? Да вроде есть, если определять ее как «переключение между лёгкими задачами без блокировки OS потоков», то в Go, конечно, она есть. Иначе как у нас могла бы быть конкурентность при GOMAXPROCS=1? На OS потоках с блокировками что ли, хахаха?

Скорее всего авторы имели в виду синтаксис JS/Python с async/await/promise, его и правда в Go нет, но причем тут синхронизация и почему кандидат должен её упомянуть — я так и не понял:)

Объяснение?

Если вы попробуете загуглить, что такое асинхронность и чем она отличается от мультипоточности, то слоп‑машина гугла ответит вам так:

Ответ AI Overview гугла на "асинхронность и многопоточность"
Ответ AI Overview гугла на «асинхронность и многопоточность»

Звучит, вроде, корректно, но что‑то тут не так. Прямой лжи тут нет.
А потом нам дается аналогия:

Аналогия от AI Overview
Аналогия от AI Overview

Вроде тоже все правильно, но....
Точно!

По этим аналогиям может сложиться впечатление, будто в многопоточности, если вы варите суп, то должны сначала его полностью доварить и не можете переключиться на другую задачу. Да и необходимость синхронизации корутин никто не отменял.
А я напомню, что в доисторические времена (25 лет назад) частенько встречались ЭВМ всего лишь с 1 ядром, при этом про корутины/горутины никто и не слышал. Как же тогда работал код, как машина не сгорала, если одновременно открывалась ICQ, Warcraft 3, Skype и IE?

Скрытый текст

Старые слабенькие компьютеры, конечно, не потянули бы сразу все приложения, но какая‑то многозадачность‑то все равно же была!

Да через те же самые потоки! Кто вообще сказал, что потоки могут параллелить только CPU‑bound? Кто сказал, что нельзя запустить 100 потоков на 1 ядре и переключать IO‑bound задачи, «вы ставите чайник на плиту,..., не блокируетесь и начинаете резать овощи».
Вы можете зайти в top/Activity Monitor/диспетчер задач и посмотреть, сколько какой процесс насоздавал потоков.
Например, на моем 8-ядерном маке у одного процесса вообще 524 потока:)

Мой Activity Monitor
Мой Activity Monitor

При этом так объясняются различия почти всегда и везде!
Тианголо в документации к FastAPI также объясняет asyncio, только через бургеры, а не супы.

Скриншот из статьи про asycnio
Скриншот из статьи про asycnio

Где‑то в интернетах есть еще такая картинка:

Опять же, она не противоречит реальности, внутри одного потока и правда в моменте может выполняться только 1 задача, корутины же позволяют переключаться между задачами внутри одного потока.
Но визуализация, почему‑то, игнорирует тот факт, что OS самостоятельно переключает потоки, при этом OS также следит за блокировками (IO‑bound операциями) и также паркует потоки.

Более корректной можно было бы визуализировать многопоточку так:

Исправленная версия
Исправленная версия

Потоки могут работать как в параллель, OS может кидать их с ядра процессора на ядро, при этом они могут и переключаться!

А как в реальности?

Термины

Процесс

Это сущность на уровне OS, имеет свою область памяти, в которую не могут ходить другие процессы и, собственно, код, который выполняется в процессе. Каждый процесс имеет минимум 1 поток на котором и работает код.

Дополнение

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

Поток/тред

Это просто последовательность инструкций, которые нужно выполнить процессору. Если поток работает слишком долго или натыкается на IO‑bound операцию, то OS может его остановить, выгрузить и запустить другой поток.

Корутина

Сущность на уровне языка программирования. Архитектура, реализация и наименование дрейфуют от языка к языку. Например, в Go это горутины, в Джаве — виртуальные треды и тд и тп. Но логика почти всегда одна и та же — корутины это те же самые потоки, но легковесные. Иногда корутинами называют останавливаемыми функциями (в Python), которые планировщик/event loop паркует и запускает, по сути как те же потоки.

Параллелизм

Свойство программы выполнять код одновременно. Не переключаться между задачами, а именно одновременно работать. Параллелизм достигается за счет многоядерности процессора и потоков: чтобы запустить N задач параллельно надо иметь хотя бы N‑ядерный процессор и N потоков.

Асинхронность

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

Конкурентность

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

Конкретнее

Разберем на питоне.
Реальность такова, что корутины выполняют ту же функцию, что и OS потоки — конкурентное выполнение кода. Иными словами, через них можно запустить 2 задачи и переключаться между ними.

GIL

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

Но в чем же отличие корутин от OS потоков? Зачем их использовать, если и то, и то дает один и тот же результат?
Корутины весят мало — 1–5 KB ОЗУ.

Так мало?

Имеется в виду сам объект корутины именно в Python. Со стеком, задачей, контекстом и всем остальным она, конечно, будет больше, но все равно намного меньше потока, который занимает мегабайты.

Переключение контекста (то есть остановка выполнения одной корутины на ядре и запуск другой на том же ядре) в корутинах быстрее — так как корутины управляются рантаймом языка, а не OS, переключение происходит в User Space, а не Kernel Space, что, банально, требует меньше операций.

Очень важно: сам факт того, что корутина не блокирует поток, в котором она выполняется, нам важен только потому, что мы не хотим лишних переключений контекстов, потому что любая блокировка может вызвать переключение в Kernel Space. При этом сами корутины, так же как и OS потоки, умеют блокироваться и переключаться:)

Интересный нюанс — то, что было описано выше, релевантно и для других языков программирования. Горутины в Go также весят мало (2–4 KB), переключаются быстро и также используют kqueue/epoll для неблокирующих обращений к OS. Отличий, конечно, тоже много, например, горутины умеют и в параллелизм.

Нюанс

В Go в принципе нет доступа к управлению OS потоками, по сути разработчик может создавать только горутины, а они умеют как в параллельность, так и в асинхронность.

Важно внести небольшую архитектурную ясность: любой процесс всегда запускает хотя бы один поток. При этом параллелизм, то есть единомоментное выполнение кода на N ядрах, возможен только при создании нескольких потоков. Поэтому, например, если вы в Go запустите 10 CPU‑bound функций в 10 горутинах в системе с 10 ядрами CPU, то у вас будет задействовано 10 OS потоков и эти 10 горутин будут работать на своих потоках.

Уточнение

Теоретически, конечно, может создаться больше потоков, например, при syscall, CGO‑вызовах и тд.

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

Асинхронность и мультипоточка в Python решают одну и ту же задачу, но отличаются только синтаксисом, экосистемой и производительностью.

Что?
Предположим, вы пишете бекенд на FastAPI (веб‑фреймворк) и ходите в Redis через redis-py и Postgres через psycopg3. Все 3 библиотеки, что я описал, умеют как в asyncio, так и в многопоточку. Вы можете написать функционально идентичный код, при этом синтаксически вам нужно будет лишь в нескольких местах поменять конфиги и проставить async и await в нужных местах. Флоу самого кода же будет идентичным.

# На потоках
@app.post("/save")
def save(kv: KV) -> None:	
  redis_client.set(kv.key, kv.value)	
  
# На корутинах
@app.post("/asave")
async def asave(kv: KV) -> None:	
  await redis_aclient.set(kv.key, kv.value)

Обе функции save и asave конкурентны: если 10 пользователей отправят единомоментно 10 запросов POST /save, FastAPI возьмёт 10 OS потоков и обработает запросы. Аналогично с POST /asave, только FastAPI запустит 10 Python корутин.

Внимательный читатель

Внимательный читатель, конечно, может предъявить, что, например, в asyncio есть cancel, а в threading — нет, а еще исключения автоматически в asyncio не пробрасываются. Но это как раз синтаксическое отличие asyncio от threading.

Также можно сказать, что asyncio красит функции, из sync функции просто не вызовещь async функцию, а sync функцию может заблокировать поток, если её неправильно вызывать из asynс. Но это и есть экосистемность.

А в других языках?

Как я уже сказал выше, в Go в принципе нет доступа к OS потокам, при этом в других языках, типа C#, Java, Kotlin, данный тезис на удивление верен (частично). Каких‑то выгод, кроме производительности, корутины дают редко.

Аналогия

Давайте попробуем придумать более корректную аналогию.

Одноядерный процессор

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

  • Берет заказ

  • Ставит бульон на готовку

  • Ждет приготовления бульона: пока тот готовится — просто смотрит

  • Снимает бульон

  • Нарезает овощи

  • Нарезает хлеб

  • Подает еду

Иными словами — делает все последовательно, без переключений и очень неэффективно!

Одноядерный процессор с OS

Добавим ему менеджера.
Теперь у него есть начальник (планировщик OS), который постоянно висит у него над душой и раз в минуту может заставить его делать другую задачу:
«Поставил готовить бульон и больше делать нечего (заблокировался)? Сходи подмети пол!»
Уже лучше. Это обычный мультитрединг на одном ядре.

Пояснение про время

Только в Linux планировщик переключает потоки, конечно, чаще, чем раз в минуту. Значения могут разниться в зависимости от настроек, версий и тд и тп, но будет порядка ~5 мс. То есть имея 10 потоков на одном ядре, каждый из которых выполняет CPU‑bound операции, переключение будет происходить каждые 5 мс. Опять же, это число — не константа и есть шедулеры, что выставляют его динамически, например, исходя из приоритетов.

Многоядерный процессор с OS

А теперь нанимаем много сотрудников (многоядерный процессор), и пусть менеджер также следит за всеми: кто‑то пошел еду разнести, кто‑то бульон варит, кто‑то убирается.
Но наш менеджер тоже немного глупый, он не знает, что дорого официантов постоянно переключать на задачи поваров, потому что тогда им приходится менять одежду, мыть руки, а вспоминать как и что готовить — медленно!

Многоядерный процессор с OS и корутинами

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

  • Принять заказ

  • Сделать заготовки

  • Приготовить бульон

  • Нарезать хлеб

  • И тд и тп

Пусть официанты теперь разносят еду, принимают заказы и взимают плату, а повара — только готовят. При этом в готовке они сами переключаются между задачами, отдельный менеджер им не нужен! (асинхронность)
Таким образом, мы сняли загрузку с менеджера (OS), ускорили переключение между задачами (корутинами) и уменьшили когнитивную загрузку работников (RAM) — им больше не нужно думать вообще про весь ресторан, только про свою зону ответственности.

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

Почему?

Почему же наши коллеги так часто объясняют асинхронность как‑то неправильно?

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

Спасибо за внимание, всех зову поспорить в комменты:)

Комментарии (17)


  1. Politura
    11.05.2026 20:31

    "во-первых нет, в Go нет асинхронности, во-вторых нужна синхронизация"

    Интересно, что было в голове у составителя ответа? Если нет асинхронности, то значит все синхронно и в таком случае никакая дополнительная синхронизация нигде не нужна. :)

    Да вроде есть, если определять ее как "переключение между лёгкими задачами без блокировки OS потоков"

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


    1. TeaDove Автор
      11.05.2026 20:31

      Боже, да, синхронизация не нужна тогда аххахааххахаха

      Попробую найти составителей опросника, может получится вразумить...


  1. Dhwtj
    11.05.2026 20:31

    Асинхронность в первую очередь про освобождение системных ресурсов при ожидании ответа.

    А как результат эта внутренняя кооперативная многозадачность даёт лёгкость в переключении, позволяющая M*N - M зелёных задач на N системных потоков, переключение в user space по await-точкам, без вытеснения.

    Если к функциям дописать ключевое слово go, они будут работать асинхронно?

    Как рантайм решит

    То есть при возврате может сменить системный поток, а может и остаться на старом. Но дебагеру это всё равно, он работает прозрачно


  1. olivera507224
    11.05.2026 20:31

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


    1. gerbert_MX
      11.05.2026 20:31

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

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


      1. olivera507224
        11.05.2026 20:31

        Несомненно, да, ты прав. Но скажи, задача, которая выполняется в разных потоках, - она выполняется асинхронно?


    1. lostero
      11.05.2026 20:31

      Асинхронность предполагает точку синхронизации (когда можно продолжить исполнение след. куска подзадачи - пресловутые await), если нет синхронизации, то её нет. Разрыв исполнения подзадачи должен быть, чтобы назвать её асинхронной. Параллельность - про исполнение подзадач одновременно. Без синхронизации в ней не будет асинхронности, т.к. не будет логического разрыва исполнения.


      1. olivera507224
        11.05.2026 20:31

        Из твоей логики получается, что две задачи, работающие в двух разных потоках на двух разных ядрах процессора, не имеющие общей точки синхронизации, работают синхронно? Так?


        1. lostero
          11.05.2026 20:31

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

          Много вопросов к сваливанию в кучу доп. абстракций разных уровней. Есть ли планировщик задач, как он работает, что есть потоки для планировщика, что есть задачи для планировщика, что за ядра процессора, какое исполнение на ядрах процессора, бесконечное и т.д. и т.п.?

          Вот пишу unikernel и организовать исполнение задач можно синхронно/параллельно/асинхронно. Можно отключить прерывания (точки синхронизации для асинхронщины) и асинхронность становится простой абстракцией без ног. Если отключить доп. ядра, то параллельность уже не добавишь никак. Если отключить и последнее ядро, то и задачки уже не порешать никак. Отсюда приходит мысль, что синхронные вычисления первичны, параллельность синхронных вторична, а асинхронность на самом деле простая абстракция без ног, которой нет разницы на чьей шее сидеть.


          1. olivera507224
            11.05.2026 20:31

            Так у нас и не спор, получается, а банальная несостыковка в понятийном аппарате :)


    1. Pand5461
      11.05.2026 20:31

      Есть ещё синхронная параллельность в виде SIMD и ILP, например. Или стрельба через винт как механическая аналогия.


      1. olivera507224
        11.05.2026 20:31

        Сорян, но в части железа не смогу поддержать качественный диалог - не чувствую себя достаточно подкованным. Могу лишь попросить объяснить и попробовать понять, не более )


  1. gpaw
    11.05.2026 20:31

    а потом приходит Эндрю Келли, выдает спич про асинхронность через I/O интерфейсы, и выносит окрашивание асинка на свалку истории.


  1. Lewigh
    11.05.2026 20:31

    Асинхронность

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

    Мне кажеться Вы сами запутались.

    Асинхронность - это свойство частей исполняемого кода работать не последовательно. Например, у нас есть задача A, для ее вычисления нам нужно еще вычислить задачи B и C. Если мы вычисляем их последовательно: A1->B->C->A2 то получаем синхронно выполненный набор задач. Если же мы ломаем последовательность вычислений, продолжая вычислять работу которая должна быть после B и C еще не имея результатов тех самых B и C , то получаем непоследовательно, ну или не синхронно ну или асинхронно выполненную последовательность.

    Причем здесь IO bound задачи в определении асинхронности? Я с тем же успехом могу асинхронно считать алгоритмы в разных потоках выполняя работу параллельно или не параллельно в одном, и так и так работа будет асинхронной.

    Как выполнять работу: на той же машине или на другой, с одном потоке или нескольких, на корутинах или потоках ОС, блокировать или не блокировать дополнительный поток - это все уже детали. Единственно важное свойство - это возможность текущему потоку управления не блокироваться в ожидании другой задачи в определенных точках. Это и есть асинхронное выполнение.


  1. rrrrt7
    11.05.2026 20:31

    Если все же немного выглянуть из мира пайтона, то базовый пример асинхронности - аппаратные прерывания. Выше уровнем - разные софтверные прерывания, сигналы и коллбэки со стороны операционки. Еще выше, использование операционкиных сисколлов со стороны пользовательских программ. Вот так например я могу вызвать у себя асинхронную запись буфера:
    sys$qio(EFN$C_ENF, chan, IO$_WRITEVBLK, &iosb, 0, 0, buf, buf_size);
    Причем вместо 0, 0, можно указать что-то типа my_ast, magic_number и тогда по завершению записи функция my_ast получит Asynchronous System Trap со стороны системы.
    И только потом уже начинается синтаксический питоновский сахар.

    С потоками тоже все чуточку сложнее. Изначально все эти позикс треды часто работали на пользовательском уровне. И только сейчас, по мере забвения древних нормальных юниксов, все привыкли что шедулинг тредов выполняется где-то в ядре с расходами на переключение контекста.
    Соответственно есть в позиксе функция pthread_attr_setscope предназначеная для выбора между System Contention Scope или Process Contention Scope треда. и т.д.


  1. Octagon77
    11.05.2026 20:31

    Мне кажется, что есть конфликт между целями

    • нужно придумать как пристроить к работе людей ещё дешевле, то есть глупее, для чего им придётся идею как-то объяснять, причём как есть - слишком сложно

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

    • нужно максимально затруднить и/или отсрочить осознание сделанного другими продавцами змеиного масла подателями свежих идей, но публично объяснять всё равно приходится, причём как есть - худший вариант.

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

    Я бы начал объяснение с того, что из последовательного расположения строчек кода никак не следует существование однопотока в Природе и задача не в том, чтобы организовать многопоточность, а чтобы применить абстракцию однопоточного выполнения. И перешёл бы к аппаратным прерываниям. Далее к JavaScript (а не Python ибо разбираться с доведённым до логического завершения проще) и Go. И поимел бы большие проблемы по трём приведённым выше причинам.

    Спрашивать вещи типа вопроса с собеседования по Go, как по мне, наиболее ужасно как раз потому, что гениальность подхода Go и состоит как раз в том, что о таком вообще не нужно думать. Не потому, что исчез повод не нужно, а потому, что следует осознанно воздерживаться от. Есть горутины и каналы - вот и ладненько. И не спрашивать как именно якобы по природе многопоточный Go работает в принципиально однопоточной WebAssembly - работает однако, вот и пусть.

    В Go удалось Event Loop и Multi-threading описать одним формализмом - это чисто математическое достижение. Радуемся.

    К слову

    Со времён знакомства с Go сохранилась функция

    func fgrm(n int, ch chan int64, d int, dLim int) (res int64) {
    	if n < 3 {
    		if ch != nil {
    			ch <- 1
    			close(ch)
    		}
    		return 1
    	}
    	if d < dLim {
    		//runtime.LockOSThread()
    		c1 := make(chan int64, 200)
    		c2 := make(chan int64, 200)
    		go fgrm(n-1, c1, d+1, dLim)
    		go fgrm(n-2, c2, d+1, dLim)
    		res += <-c1
    		res += <-c2
    	} else {
    		res = fgrm(n-1, nil, n+1, dLim) + fgrm(n-2, nil, n+1, dLim)
    	}
    	if ch != nil {
    		ch <- res
    		close(ch)
    	}
    	return
    }

    Я до сих пор помню удивление как на 4-ядерном процессоре время выполнения бодро падало при росте dLim от 1 до 5, а при переходе от 5 к 6 увеличилось в 250 раз.


  1. werevolff
    11.05.2026 20:31

    Саллахадин Джуба в своей книге про PostgreSQL даёт точное определение асинхронности. Моими словами:

    асинхронность - это процесс одновременного выполнения задач, при котором мы точно не знаем: в каком порядке эти задачи завершатся.

    Отсюда следуют такие выводы:

    Конкурентность - это процесс выполнения задач, при котором одновременно выполняется только одна задача, которая может отдавать или терять управление, передавая его другой задаче.

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

    Иными словами, параллельность всегда будет асинхронной, поскольку мы не знаем точно, в каком порядке выполнятся наши задачи. Конкурентность может быть асинхронной или не быть асинхронной, поскольку мы можем управлять порядком выполнения задач.

    В пайтоне GIL не наглухо блокирует потоки. Во второй версии языка он переключал потоки при выполнении определённого количества инструкций. С третьей версии триггер переключения заменён на время выполнения, после которого поток отдаёт управление. Поэтому, многопоточность в пайтоне 100% асинхронная и может применяться на CPU-bound операциях. Но не параллельная, поскольку одновременно выполняется только один поток. Конкурентная. Также, numpy может обходить GIL и делать операции параллельными. Это исключение.

    Asyncio - конкурентный, асинхронный. Но передача управления происходит только на IO-bound операциях. Не параллельный никогда.

    Асинхронность, конкурентность и параллельность - это стили программирования. Программист выбирает тот подход, который будет использоваться при выполнении его кода. Например, запуская умножение векторов через numpy, он может рассчитывать на параллелизм вычислений. А при использовании asyncio, он должен заботиться о конкурентности. Как и при использовании многопоточности, поскольку его треды могут переключаться.