Я пришел сюда довольно комфортно с этими двумя концепциями, но с чем-то неясным для меня в них.
Прочитав некоторые ответы, я думаю, что у меня есть правильная и полезная метафора, чтобы описать разницу.
Если вы думаете о своих отдельных строках кода как об отдельных, но упорядоченных игральных картах (остановите меня, если я объясняю, как работают перфокарты старой школы), то для каждой отдельной написанной процедуры у вас будет уникальная стопка карт (не копировать и вставить!), а разница между тем, что обычно происходит при обычном и асинхронном запуске кода, зависит от того, заботитесь вы или нет.
Когда вы запускаете код, вы передаете ОС набор отдельных операций (на которые ваш компилятор или интерпретатор разбил код «более высокого» уровня) для передачи процессору. С одним процессором одновременно может выполняться только одна строка кода. Итак, чтобы создать иллюзию одновременного запуска нескольких процессов, ОС использует метод, при котором она отправляет процессору только несколько строк из данного процесса за раз, переключаясь между всеми процессами в зависимости от того, как он видит поместиться. В результате несколько процессов показывают прогресс конечному пользователю, кажется, в одно и то же время.
Для нашей метафоры связь состоит в том, что ОС всегда перемешивает карты перед отправкой их процессору. Если ваша стопка карт не зависит от другой стопки, вы не заметите, что ваша стопка перестала выбираться, когда другая стопка стала активной. Так что, если вам все равно, это не имеет значения.
Однако, если вам все равно (например, существует несколько процессов - или стопок карт - которые зависят друг от друга), то перетасовка ОС испортит ваши результаты.
Написание асинхронного кода требует обработки зависимостей между порядком выполнения, независимо от того, каким в конечном итоге будет этот порядок. Вот почему используются такие конструкции, как «обратные вызовы». Они говорят процессору: «Следующее, что нужно сделать, это сообщить другому стеку, что мы сделали». Используя такие инструменты, вы можете быть уверены, что другой стек получит уведомление до того, как он разрешит ОС выполнять какие-либо другие свои инструкции. («Если called_back == false: send (no_operation)» - не уверен, что это действительно так, но логически я думаю, что это согласованно.)
Для параллельных процессов разница в том, что у вас есть два стека, которые не заботятся друг о друге, и два воркера для их обработки. В конце концов, вам может понадобиться объединить результаты из двух стеков, что тогда будет вопросом синхронности, но для выполнения вам снова все равно.
Не уверен, что это помогает, но я всегда нахожу полезными несколько объяснений. Также обратите внимание, что асинхронное выполнение не ограничивается отдельным компьютером и его процессорами. Вообще говоря, он имеет дело со временем или (даже в более общем смысле) с порядком событий. Поэтому, если вы отправляете зависимый стек A на сетевой узел X, а его связанный стек B - на Y, правильный асинхронный код должен учесть ситуацию, как если бы он работал локально на вашем ноутбуке.