Многопоточно: асинхронное синхронно
Да простит меня певец Дима Билан, ведь название этого поста есть ничто иное, как аллюзия на одну из его песен. Даа, были времена...
Но теперь серьёзно. Многопоточность и асинхронность, это мало того, что разные вещи, но это ещё и одно и то же. Потому что ни в одном другом, возможно, разделе знаний о программировании нет такой чудовищной путаницы в терминах. Разные языки, разные школы, разные эпохи использовали и продолжают использовать эти слова в своих целях, наделяя их то одними, то другими значениями. Изначально, очень давно, многопоточность была изобретена для того, чтобы выполнять программные задачи асинхронно. Но сейчас во многих языках уже есть более современное понятие асинхронности, которое не является классической многопоточностью, но технически опирается именно на неё. Тут можно сломать мозг, просто поговорив с десятком разных людей об одном, казалось бы, и том же: каждый будет понимать эту проблематику по-своему.
Нам нужно разобраться во всём этом раз и навсегда. Что такое асинхронность? А синхронность? И если есть многопоточность, то должна быть малопоточность, или как?
Терпение. Начнём издалека, с исторической справки. Это, в целом, будет общий алгоритм рассмотрения тем в данном блоге (это ведь блог, да? [картинка с прекрасной, наивной и такой молодой Натали Портман]). Известно, что технологии развиваются от простого к сложному, и каждый новый виток обусловлен либо ограничениями, заложенными ранее, либо стремлением эти ограничения разрушить. Мало взять актуальную картину мира, желательно всегда смотреть в корень, с чего всё начиналось, и тогда станет ясно, почему всё пришло к тому, что есть на сегодняшний день. Читайте внимательнее, и вы увидите, как кирпичики будут ложиться один к одному, и в итоге, образуют собой причудливое знание. Здание. Ну, не важно.
На заре компьютерной эпохи, перед любыми вычислительными машинами стояла ровно одна задача: выполнять математические расчёты быстрее, точнее и стабильнее, чем человек. Нужно было заменить толпу высокообразованных, немношк медленных, иногда ошибающихся, дорогих специалистов по вычислениям одним большим железным ящиком, который делал бы их работу лучше них. Пропустим, наверное, эпоху вычислительных машин на основе электро-механических реле и прочей гериатрической техники, и шагнём сразу в электронную эру, т.е., конец 40-х годов XX века, и позже. По сути своей, первые ЭВМ, это были улучшенные арифмометры: они могли не просто складывать, вычитать, умножать и делить, но и выполнять эти действия по списку, одно за другим, автоматически. И ещё, ветвить логику в зависимости от условий. Роль оператора ЭВМ была в том, чтобы составить для машины программу вычислений (или, сокращённо, программу), загрузить в "ящик" (именно загрузить, т.е., положить внутрь некий физический носитель), запустить процесс (хотя процессоров сегодняшнего типа тогда ещё и не было), получить результаты, и далее как-то их интерпретировать, согласно постановке своей исходной задачи. Физические носители представляли собой так называемые перфоленты (ленты с отверстиями, характер размещения которых задавал те или иные операции), а позже - перфокарты, которые уже хотя бы не рвались в ненужный момент. Устройство "читало" ленты и карты последовательно, и выполняло зашифрованные в них машинные команды (грубо говоря, что к чему прибавить, что отнять, и пр., только на низком, машинном уровне), модифицируя рабочие данные, и переходило к следующему шагу. Последовательно. То есть, в одном потоке команд и данных. Одна операция в единицу времени. И это именно то, что было тогда нужно. Естественно, никаких языков программирования, операционных систем и всего прочего тогда ещё не было. Всё максимально утилитарно: засыпали кофейные зёрна, получили молотый кофе. Это и есть синхронность, как её понимают в ИТ.
Но вообще, тут надо бы оговориться. Все мы знаем, что есть, например, синхронное плавание. Или, вот, военные идут строем во время парада - они движутся абсолютно синхронно. Или, плывёт косяк рыбок в океане, и вдруг все они синхронно меняют направление. Тут можно было бы подумать, что синхронность, это когда несколько разных объектов совершают действия одновременно, т.е., имеется не один поток вычислений, а множество. Но: в программировании, это не так. Нужно просто запомнить, что синхронно - это последовательно, а не параллельно. И, стало быть, наоборот.
Технический прогресс неумолим и беспощаден. На смену радиолампам пришли транзисторы. Громадные конструкции из проводов, катушек, реле, металлических шкафов уступили место миниатюрным интегральным схемам на полупроводниках. Компьютеры становились всё меньше и экономичнее, мощнее, и в то же время дешевле. Ну и бла, бла, бла, в итоге люди поняли, что им тесна изначальная парадигма последовательного выполнения операций. Почему нельзя их делать параллельно, или хотя бы как-то имитировать это для внешнего мира? Ведь использовать один компьютер для решения нескольких задач одновременно, это эффективнее, чем использовать одновременно несколько компьютеров, каждый для решения одной задачи. Это просто намного дешевле должно быть. Это надо было изобрести, т.к. игра явно стоила свеч.
Думаю, пора очертить примерный круг вопросов, проблем и их решений, которые будут затронуты в дальнейших постах по данной тематике.
1. Устройство микропроцессора. Как физически происходит обработка сигналов. Зачем в процессоре столько транзисторов, и что ещё там есть. Как внутри него представлены данные, что значит их обработка. При чём тут конвейер, что такое регистры. Что такое кэш уровней 1 и 2, и почему производители гордятся их размером, но слишком много памяти туда не припаивают всё равно. Что такое контекст (исполнения), как, когда и зачем осуществляется его переключение. Что такое ядро (core), и что такое поток (thread), и в каких они отношениях. Циклы, такты, генератор частоты, машинные команды, прерывания, Винни Пух и все-все-все.
2. Операционная система. Что такое процесс, и как он связан с программой. Потоки (вот! началось! потоки уже были, и тут снова они, но в другом смысле). Выполнение нескольких потоков на одном физическом ядре процессора. И, наоборот, работа с многоядерными процессорами ("много", это не только 2 или 4, но и 64 или, скажем 4 CPU по 64 ядра). Системные средства взаимодействия потоков и процессов. Разделяемые ресурсы операционной системы. Обзор эволюции операционных систем от состояния "предоставляю аппаратные ресурсы по требованию программ" к состоянию "сама решаю, какую программу когда выполнять, и обеспечиваю процесс переключения".
3. Многопоточное программирование. Цели, ради которых надо было так всё усложнять. Трудности синхронизации потоков (в смысле, синхронизации? они же параллельные?). Конкуренция за ресурсы, обеспечение целостности данных. Проблемы производительности. Утрата управляемости. Методики избежания проблем путём создания других проблем. Бег по граблям, задорные конкурсы, и возврат к началу эволюции. Разница между работой с многопоточностью в настольных однопользовательских приложениях и в клиент-серверных системах, ориентированных на массовую обработку одновременных внешних запросов.
4. Понятие асинхронности. Главное идейное отличие от многопоточности. Идейные сходства. Вывод о том, что это всё-таки не одно и то же. Выяснение вопроса, при чём здесь таймеры и машина статусных переходов, аппаратные прерывания и переключение контекста исполнения машинных команд. Почему неблокируемые вызовы на самом деле блокируются, но не в том смысле, как некоторые думают. Ключ к масштабируемости: как асинхронность позволяет выжимать из оборудования всё и откладывать модернизацию ИТ-инфраструктуры веб-приложений до лучших времён. Вообще, по опыту, нередко именно понятие асинхронности даётся людям наиболее тяжело. Этим постом мы должны расставить все точки над i.
И, конечно же, по мере продвижения по материалу, мы будем выстраивать в своей голове непротиворечивую мозаику терминов, отсеивая неправильные трактовки и неоднозначности, являющие собой богатейший источник каверзных вопросов на собеседованиях.
Что ж, удачи нам. Она понадобится.