Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Конкурентность позволяет эффективно выполнять компьютерные программы, разделяя их на задачи, которые можно запускать независимо. Такой подход помогает ускорить игровую графику, обучать большие модели искусственного интеллекта, быстро масштабировать веб-приложения, оптимизировать обработку больших данных и решать многие другие задачи. Работать с конкурентностью непросто, так что эта книга постепенно введет вас в курс дела, а помогут в этом интересные примеры, забавные иллюстрации и понятный код на Python. Вы изучите приемы, с помощью которых сможете программировать многоядерные и графические процессоры, а так же другие высокопроизводительные системы. Кирилл Бобров обходится без сложной математики, технического жаргона и тяжеловесных научных рассуждений, предпочитая простые и доступные объяснения.
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 271
Veröffentlichungsjahr: 2024
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Переводчики Е. Матвеев, Е. Матвеев
Кирилл Бобров
Грокаем конкурентность. — СПб.: Питер, 2024.
ISBN 978-5-4461-4102-9
© ООО Издательство "Питер", 2024
Я рад приветствовать вас на страницах перевода моей книги «Grokking Concurrency». Для меня большая честь представить вам эту работу на моем родном языке.
Конкурентное программирование — штука не из легких, но чертовски важная. В начале карьеры инженеры часто стараются обходить эту тему стороной, как кот старается избежать воды. Но рано или поздно столкнуться с ней все равно придется. Возможно, вам понадобится масштабировать вашу систему, написать потокобезопасный код или исправить баг в уже существующем. Многоядерные процессоры и распределенные системы уже давно стали нормой, и умение эффективно использовать и правильно работать с конкурентностью необходимо для создания высокопроизводительных приложений.
На русском языке термин «concurrency» может означать много разных вещей, включая «параллелизм», «многозадачность», «асинхронность» и «согласованность». Все эти вещи важны для понимания конкурентности, и я постарался объяснить их как можно проще и понятнее в этой книге. А команда издательства «Питер» постаралась перевести их максимально точно для вас, читателей.
Вместо сухого перечня API и списка классов я знакомлю вас с концепциями, сущностями, паттернами и моделями, которые не зависят от языка программирования и всегда будут актуальны. А чтобы чтение было веселее, я добавил пару историй и примеров из жизни. Надеюсь, они помогут вам лучше понять материал и сделают процесс обучения интереснее.
Эта книга написана с целью сделать сложные концепции доступными и понятными для каждого в стиле серии Grokking. Надеюсь, что она поможет вам разобраться в конкурентном программировании и вдохновит на создание новых крутых проектов. Ваш интерес и стремление к знаниям — это то, что движет мир технологий вперед.
С нетерпением жду ваших отзывов и впечатлений. Желаю вам успешного изучения и применения полученных знаний!
Кирилл Бобров
Посвящаю родителям — Елене и Андрею, замечательной паре, которая создала этот шедевр (меня); а также моей жене Кате, которая помогла мне не сойти с ума в этом мире, полном багов и накладок.
Представьте себе мир, в котором за развитием технологии угнаться сложнее, чем за гепардом, накачанным стимуляторами, а спрос на эффективное конкурентное программирование неудержимо растет. В этом мире разработчики сталкиваются с серьезной задачей: как создавать системы, которые способны справиться с колоссальными объемами данных и обрабатывать их достаточно проворно, чтобы удовлетворить неутолимые потребности пользователей? В этом мире конкурентность не только пленила умы, но и стала головоломкой, которую приходится разгадывать. И в этом мире мы живем сегодня.
Когда-то и я попался на эту приманку. Затем мне встретились понятия «конкурентность» и «асинхронность». И то, что я обнаружил, оказалось настоящей золотой жилой: местом неосвоенной силы, которая может превратить обычную программу во впечатляющую манифестацию вычислительной мощи. Однако сокровища залегали под массивными пластами сложности, а разные части головоломки: конкурентность, параллелизм, потоки, процессы, многозадачность, корутины — были разбросаны по всему технологическому ландшафту. Мне отчаянно не хватало наставника, который собрал бы все составляющие воедино и представил полную картину. Я так и не смог найти ресурс, который бы заполнял пробелы между теорией и практикой в разных языках программирования, поэтому решил создать такой ресурс своими силами. Так родилась книга «Грокаем конкурентность», которая станет вашим верным помощником, будет сопровождать вас, нашептывать секреты и освещать дорогу сквозь лабиринты на пути к мастерству.
Эта книга — не типичное техническое пособие; она призвана увлечь вас, вплетая в материал занимательные истории и пасхалки. Книга выросла из теоретического руководства, насыщенного побасенками, культурными отсылками (сумеете обнаружить их все?) и забавными иллюстрациями. Эта книга не стесняется ни юмора, ни глубокой привязанности к пельменям и пицце — кто сказал, что изучать конкурентность нужно уныло и формально?
Совместными усилиями мы справимся со сложностями конкурентности и раскроем тайны асинхронности. На всем пути, от азов конкурентности до async и await, нас будет сопровождать верный спутник Python — язык программирования, который я выбрал для этой книги. Не беспокойтесь, если это не ваш основной язык: понятия и приемы, о которых пойдет речь, не привязаны ни к какому конкретному языку.
«Но почему Python?» — спросите вы. Дело в том, что этот язык выдерживает идеальный баланс между простотой и выразительностью, благодаря чему мы сможем сосредоточиться на сути конкурентности и не отвлекаться на второстепенные мелочи. Кроме того, не буду скрывать, что Python мне просто нравится.
В этой книге найдется что-то полезное для самых разных читателей — как для опытных разработчиков, которые стремятся глубже разобраться в конкурентных системах, так и для любознательных новичков, которые только осваивают основы конкурентности. Вместе мы раскроем секреты конкурентного выполнения, и вы научитесь строить масштабируемые, эффективные и гибкие программные системы, которые способны справиться с любыми проблемами.
Приготовьтесь отправиться в совершенно уникальное путешествие, где размываются границы времени и пространства, а программы танцуют под ритмы осьминогов. Да, вы правильно прочитали — осьминогов. Эти очаровательные обитатели морских глубин, чьи восемь щупалец извиваются в идеальной гармонии, символизируют нетривиальную, завораживающую природу конкурентных систем, которые нам с вами предстоит исследовать. Итак, в путь!
Прежде чем погружаться в тонкости конкурентности, я хочу выразить благодарность замечательным людям, без которых эта книга не увидела бы свет. Говорят, что написать книгу — все равно что пробежать марафонскую дистанцию, но я бы сказал, что это больше похоже на головокружительную поездку на американских горках. И как же я рад, что все эти люди были рядом!
Прежде всего я безгранично благодарен своей жене Екатерине Кривец, которая считает, будто я — творческая личность, хотя именно ей принадлежат все потрясающие иллюстрации в этой книге.
Хочу также поблагодарить всех родных и близких. Их любовь и поддержка неустанно вдохновляли меня в этом проекте, и мне повезло, что такие люди есть в моей жизни.
Я глубоко признателен группе поддержки, которая содействовала мне на всех этапах работы. Отдельные благодарности — Кристине Ялышевой, Михаилу Полторацкому, Татьяне Бородиной, Андрею Гаврилову и Александру Бельницкому, которые всегда верили в меня и были готовы прийти на помощь. Хочу особо поблагодарить Веру Кривец за то, что помогала мне совершенствоваться в английском.
Отдельно упомяну Берта Бейтса (Bert Bates) и Брайана Ханафи (Brian Hanafee); их наставничество и конструктивные соображения научили меня по-новому преподавать и излагать сложные концепции. Спасибо за ваши бесценные советы и вклад в мое становление как автора.
Я глубоко благодарен потрясающей команде издательства Manning Publications. Майк Стивенс (Mike Stephens), мое приключение началось благодаря тебе, и я неимоверно рад, что ты поверил в меня. Иэн Хаф (Ian Hough) был моим наставником, терпеливо прорабатывал со мной главу за главой и приводил в порядок мой английский. Он заслуживает самой высокой награды за то, что пережил процесс редактирования! Спасибо Артуру Зубареву, который одолел мои неуклюжие черновики и поделился ценной обратной связью. Лу Кови (Lou Covey), прости за периодические размолвки, но твои истории всегда были интересными и поддерживали меня в работе. Марк Томас (Mark Thomas), твоя научная редактура и помощь с программным кодом сильно изменили книгу к лучшему. Тиффани Тейлор (Tiffany Tailor), без твоей внимательности и опыта текст не получился бы таким ясным и связным. Кэти Теннант (Katie Tennant), благодаря твоей скрупулезности и ценным редакторским замечаниям материал удалось отшлифовать и подготовить к публикации.
Мои рецензенты, спасибо вам всем: Абхиджит Наяк (Abhijith Nayak), Амрах Умудлу (Amrah Umudlu), Андрес Сакко (Andres Sacco), Арно Байи (Arnaud Bailly), Балбир Сингх (Balbir Singh), Биджит Комалан (Bijith Komalan), Клиффорд Тёрбер (Clifford Thurber), Дэвид Якобович (David Yakobovitch), Дмитрий Воробьев, Эдду Мелендес (Eddu Melendez), Эрнесто Арройо (Ernesto Arroyo), Эрнесто Босси (Ernesto Bossi), Эшан Тендон (Eshan Tandon), Эзра Шрёдер (Ezra Schroeder), Франс Ойлинки (Frans Oilinki), Ганеш Сваминатан (Ganesh Swaminathan), Гленн Госсенс (Glenn Goossens), Грегори Варгис (Gregory Varghese), Имакулет Ресто Моша (Imaculate Resto Mosha), Джеймс Чжицзюнь Лю (James Zhijun Liu), Иржи Чинчура (Jiří Činčura), Джонатан Ривз (Jonathan Reeves), Лаванья М.К. (Lavanya M K), Люк Рогге (Luc Rogge), Манодж Редди (Manoj Reddy), Мэтт Гуковски (Matt Gukowsky), Мэтт Вельке (Matt Welke), Микаэль Дотри (Mikael Dautrey), Нолан То (Nolan To), Оливер Кортен (Oliver Korten), Патрик Гётц (Patrick Goetz), Патрик Реган (Patrick Regan), Рагунат Джавахар (Ragunath Jawahar), Саи Хегди (Sai Hegde), Серхио Арбео Родригес (Sergio Arbeo Rodríguez), Широшика Кулатилаке (Shiroshica Kulatilake), Венката Нагиндра Бабу Янамадала (Venkata Nagendra Babu Yanamadala), Виталий Ларченков и Уильям Джамир (William Jamir). Ваши замечания помогли улучшить книгу.
Кроме того, хочу обратиться к безымянным героям, которые неустанно трудились за кулисами, чтобы эта книга вышла в свет. Вы знаете, о ком я говорю, и хотя у вас нет эффектного супергеройского плаща, вы — тот «секретный ингредиент», без которого ничего бы не получилось. Вы — настоящие суперзвезды!
Наконец, как сказал бы Снуп Догг, я хочу поблагодарить себя, без которого эта книга никогда бы не написалась!
Эта книга была написана для того, чтобы развеять мистический ореол, который окружает конкурентность, асинхронность и параллельное программирование, познакомить вас с фундаментальными основами этих понятий и продемонстрировать, как они применяются на практике. В отличие от научных исследований или работ, посвященных конкретным языкам, эта книга стремится объяснять базовые идеи и принципы, а не отдельные реализации. Здесь рассматриваются высокоуровневые понятия, материал изложен доступным языком, а вместо сложных математических выкладок используются наглядные диаграммы; все это помогает лучше разобраться в предмете. Знания, которые вы получите, помогут ориентироваться в конкурентных фреймворках и разрабатывать масштабируемые решения в интересующей вас области. Книга в некоторой степени восполняет нехватку доступных ресурсов по этой теме; это подробное и доходчивое руководстство для читателей, которые хотят разобраться в понятиях конкурентности и асинхронности. Она сэкономит время программистам, которым понадобились бы годы практической работы, чтобы самостоятельно получить те же знания.
Книга идеально подойдет для всех, кто хочет изучить основы конкурентности. Чтобы извлечь из материала максимальную пользу, желательно в общих чертах разбираться в том, как работают компьютерные системы, понимать основные концепции и структуры данных языков программирования, а также иметь опыт написания последовательных программ. Предварительные знания об операционных системах не нужны: вся необходимая информация есть в книге. Хотя здесь фигурируют понятия из области компьютерных сетей, они не объясняются во всех подробностях: книга предполагает, что вы немного представляете себе, как устроены сети. Вам не обязательно глубоко разбираться во всех этих областях, а если понадобится подтянуть свои знания по той или иной теме, этим можно будет заниматься по ходу дела.
Книга делится на три части. В части I «Оркестр осьминогов. Симфония конкурентности» рассматриваются фундаментальные понятия и примитивы написания конкурентных программ. Главы 1–5 посвящены основным принципам конкурентности на разных уровнях — от аппаратного до прикладного.
В части II «Щупальца конкурентности. Многозадачность, декомпозиция и синхронизация» идет речь о том, как абстракции и популярные паттерны помогают сделать код более производительным, масштабируемым и гибким. В главах 6–9 вы научитесь преодолевать распространенные проблемы, которые возникают при разработке конкурентных систем.
Часть III «Асинхронные осьминоги. История о конкурентном выполнении со вкусом пиццы» расширяет ваши знания в области конкурентности: вместо одной машины вы будете иметь дело с несколькими машинами, которые связаны по сети. В этом контексте события могут происходить асинхронно, то есть разные события не обязательно привязаны друг к другу по времени. Понятие асинхронности занимает центральное место в главах 10–12, где эта тема освещается в разных измерениях. С помощью асинхронности создается иллюзия конкурентного или параллельного выполнения задач, однако в современных реализациях можно совмещать асинхронные операции с действительно конкурентными, что позволяет значительно повысить производительность системы. Книгу завершает глава 13, где собраны задачи из области конкурентности. Мы решим их шаг за шагом, чтобы убедиться в том, что вы действительно освоили тему конкурентности.
Исполняемые фрагменты кода можно загрузить из онлайн-версии этой книги по адресу https://livebook.manning.com/book/grokking-concurrency. Полный код примеров из книги доступен на сайте издательства Manning (https://www.manning.com) и на GitHub (https://github.com/luminousmen/grokking_concurrency). Исходный код задуман как справочник о том, как можно реализовывать программы. Эти примеры оптимизированы для обучения, а не для реальной эксплуатации. Код специально составлен так, чтобы служить учебным пособием. В реальных проектах стоит использовать проверенные библиотеки и фреймворки, потому что они обычно оптимизированы для производительности, хорошо протестированы и качественно поддерживаются.
Приобретая книгу «Грокаем конкурентность», вы также получаете бесплатный доступ к платформе для онлайн-чтения liveBook издательства Manning (на английском языке). Эксклюзивные возможности liveBook позволяют оставлять комментарии как к книге в целом, так и к отдельным ее разделам или абзацам. Можно легко делать заметки для себя, задавать технические вопросы и отвечать на них, а также получать помощь от авторов и других пользователей. Чтобы получить доступ к форуму, посетите страницу https://livebook.manning.com/book/grokking-concurrency/discussion. Информацию о форумах Manning и правилах поведения на них см. на https://livebook.manning.com/discussion.
В рамках своих обязательств перед читателями издательство Manning предоставляет ресурс для содержательного общения читателей и авторов. Эти обязательства не подразумевают конкретную степень участия автора, которое остается добровольным (и неоплачиваемым). Задавайте авторам хорошие вопросы, чтобы им было интересно участвовать в диалоге! Форум и архивы обсуждений доступны на сайте Manning, пока книга продолжает издаваться.
Кирилл Бобров — опытный разработчик с непростым характером, который отлично разбирается в разработке и проектировании высоконагруженных приложений. Питая страсть к инженерии данных, он направляет усилия на то, чтобы реализовывать передовые практики инженерии данных для компаний по всему миру. А еще этот угрюмый тип выступает в образе симпатичного кота, который ведет популярный иллюстрированный техноблог https://luminousmen.com.
Ваши замечания, предложения, вопросы отправляйте по адресу [email protected] (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
Вот вы сидите в кофейне за чашечкой капучино, и вдруг ваше внимание привлекает группа программистов за соседним столиком, которые с энтузиазмом беседуют о конкурентности. Они легко жонглируют разными терминами — «параллельные вычисления», «потоки», «межпроцессная коммуникация», — от которых у вас голова идет кругом. Не огорчайтесь, вы в этом не одиноки.
Все, кто бывал на концертах симфонической музыки, слышали характерный эффект, когда во время настройки перед концертом музыканты одновременно играют на разных инструментах разные мелодии. Это прекрасный хаос, который сам по себе подобен своеобразному музыкальному произведению. У него много общего с явлением конкурентности: существуют несколько процессов или потоков, которые выполняются одновременно, чтобы достичь общей цели.
В главах 1–5 представлены основы конкурентности, некоторые принципы работы компьютеров и разные виды примитивов конкурентности. Мы рассмотрим последовательные и параллельные вычисления, узнаем, какие аппаратные и программные компоненты нужны для конкурентного выполнения, а также изучим разные типы межпроцессной коммуникации, которые позволяют нескольким процессам органично работать совместно.
Итак, возьмите чашечку капучино и присоединяйтесь к беседе. Обещаю, что вы не пожалеете о потраченном времени.
В этой главе
• Почему конкурентность — важная тема, которую стоит изучить
• Как измерять производительность систем
• Какие бывают уровни конкурентности
Выгляните в окно и повнимательнее присмотритесь к окружающему миру. Разве все вокруг происходит по линейной, последовательной схеме? Нет, скорее, вы убедитесь, что мир больше похож на сложное переплетение компонентов, которые все одновременно функционируют независимо друг от друга и при этом взаимодействуют друг с другом.
Хотя люди склонны мыслить последовательно — например, когда они просматривают список текущих дел и выполняют задачи одну за другой, — реальный мир устроен намного сложнее. В нем гораздо больше параллельного, чем последовательного. Взаимосвязанные события случаются одновременно. Конкурентность окружает нас повсюду — от хаотичной толкотни в людном супермаркете до слаженных перемещений футболистов на поле и постоянно меняющегося потока дорожного движения. Подобно реальному миру, компьютеры должны уметь выполнять операции в конкурентном режиме, чтобы с их помощью можно было моделировать, имитировать и анализировать сложные явления реального мира.
Принцип конкурентности в компьютерных технологиях заключается в том, что система работает сразу с несколькими задачами. Такой системой может быть программа, компьютер или сеть компьютеров. Без конкурентных вычислений наши приложения неизбежно отстали бы от сложности окружающего мира.
Но когда мы начинаем глубже погружаться в тему конкурентности, могут возникнуть определенные вопросы. Первый вопрос (на случай, если я вас еще не убедил) — почему вообще стоит уделять внимание конкурентности?
Конкурентность играет чрезвычайно важную роль в разработке программных продуктов. Спрос на высокопроизводительные приложения и конкурентные системы таков, что конкурентное программирование становится критически важным навыком для разработчиков.
Конкурентное программирование — не новое понятие, но за последние годы внимание к нему существенно усилилось. По мере того как в современных компьютерных системах растет количество ядер и процессоров, конкурентное программирование превращается в необходимое требование для разработки ПО. Компании ищут разработчиков, которые уверенно владеют конкурентным программированием, потому что оно часто оказывается единственным выходом в ситуациях, когда нужна высокая производительность, но вычислительные ресурсы ограничены.
Самое значительное преимущество конкурентного программирования (и исторически первая причина для исследований в этой области) состоит в том, что оно помогает повысить производительность систем. Разберемся, почему это важно.
Если нужно повысить производительность, почему бы просто не купить более быстрые компьютеры? Десятки лет назад так и поступали, но в какой-то момент выяснилось, что быстрые компьютеры уже не решают проблему.
В 1965 году Гордон Мур, один из основателей Intel, обнаружил любопытную закономерность. Новые модели процессоров появлялись примерно через два года после своих предшественников, и каждый раз количество транзисторов на них приблизительно удваивалось. Мур заключил, что количество транзисторов — и, как следствие, тактовая частота процессоров — удваивается через каждые 24 месяца. Это наблюдение стало известно как закон Мура. Для разработчиков это означало, что нужно было подождать всего два года, чтобы приложение стало работать вдвое быстрее.
Проблема в том, что около 2002 года правила изменились. Как сказал знаменитый эксперт по C++ Херб Саттер (Herb Sutter), «бесплатные обеды закончились»1. Обнаружилась фундаментальная связь между физическим размером процессора и скоростью обработки (частотой процессора). Время, за которое выполняется операция, зависит от длины цепи и скорости света. Проще говоря, транзисторы (главные структурные блоки компьютерных микросхем) нельзя добавлять в процессор бесконечно. Немаловажную роль играет и повышение температуры. Стало невозможно улучшать производительность только за счет того, чтобы повышать частоту процессора. С этого момента начался так называемый кризис многоядерности.
Развитие отдельных процессоров (в смысле повышения тактовой частоты) остановилось из-за физических ограничений, а необходимость повышать производительность систем осталась. Производители переключились на горизонтальное расширение в форме многопроцессорных систем, что заставило разработчиков, архитекторов и создателей языков адаптироваться к архитектурам со множественными вычислительными ресурсами.
Главный вывод из этого исторического обзора таков: важнейшим преимуществом конкурентности (и исторически первой причиной для исследований в этой области) стало то, что она позволяет повысить производительность системы так, чтобы при этом эффективно использовать дополнительные вычислительные ресурсы. И здесь возникают два важных вопроса: как измерить эту производительность и как ее улучшить?
В компьютерных технологиях производительность можно измерять по-разному в зависимости от того, как мы рассматриваем вычислительную систему. Один из способов повысить объем работы — сократить время, за которое выполняются отдельные операции.
Допустим, вы ездите из дома на работу на мотоцикле, и дорога в одну сторону занимает один час. Для вас важно, насколько быстро вы добираетесь до работы, поэтому вы измеряете производительность системы по этой метрике. Если вы будете ехать быстрее, то быстрее окажетесь на работе. С точки зрения компьютерной системы такой эффект называется задержкой (latency). Задержка — это метрика, которая измеряет, сколько времени занимает выполнение отдельной задачи от начала до конца.
Теперь представьте, что вы работаете в управлении общественного транспорта и вам поручено повысить производительность автобусной сети. На этот раз ваша задача не в том, чтобы один человек доехал до работы быстрее, а в том, чтобы увеличить количество людей, которые добираются из дома на работу в единицу времени. Такой эффект называется пропускной способностью (throughput): эта метрика измеряет количество задач, которые система может выполнить за тот или иной период времени.
Очень важно понимать, чем задержка отличается от пропускной способности. Даже если мотоцикл едет вдвое быстрее автобуса, пропускная способность автобуса в 25 раз больше. (Мотоцикл перевозит одного человека на заданное расстояние за 1 час, тогда как автобус перевозит 50 человек на то же расстояние за 2 часа: если усреднить по времени, получается 25 человек в час.) Иначе говоря, более высокая пропускная способность системы не обязательно означает более низкую задержку. Если оптимизировать производительность, то улучшение по одному показателю (например, по пропускной способности) может привести к ухудшению по другому (например, по задержке).
Конкурентность помогает снизить задержку. Например, если задача занимает много времени, ее можно разбить на меньшие задачи, которые выполняются параллельно, что сократит общее время выполнения. Конкурентность также помогает повысить пропускную способность, если несколько задач обрабатываются одновременно.
Кроме того, конкурентность может скрывать задержку. Когда вы ждете важного звонка, автобуса до работы и т.д., можно просто ждать, а можно направить вычислительные ресурсы на другую деятельность. Например, в ожидании автобуса можно проверить электронную почту. Таким образом вы фактически выполняете несколько задач одновременно и скрываете задержку благодаря тому, что эффективно используете время ожидания. Скрытие задержки играет ключевую роль при разработке систем с малым временем отклика и применяется в задачах, где фигурирует ожидание.
Итак, конкурентность может улучшить производительность системы по трем основным направлениям:
• сократить задержку (чтобы единица работы выполнялась быстрее);
• скрыть задержку (чтобы система делала что-то еще во время операции с высокой задержкой);
• повысить пропускную способность (чтобы система выполняла больше работы в единицу времени).
Мы увидели, как конкурентность помогает повысить производительность, а теперь рассмотрим, для чего еще она применяется на практике. Ранее в этой главе упоминалось, что конкурентность необходима, чтобы моделировать сложный мир, который нас окружает. Теперь можно конкретнее поговорить о том, как с ее помощью решать большие или сложные задачи вычислительными средствами.
Многие задачи, которые стоят перед разработчиками систем, имеющих отношение к реальному миру, настолько сложны, что решать их в последовательной системе практически нереально. Сложность может быть обусловлена размером задачи или тем, насколько трудно разобраться в отдельной части разрабатываемой системы.
Размер задачи связан с масштабируемостью (scalability); это характеристика системы, производительность которой можно повысить, если добавить дополнительные ресурсы. Масштабировать систему можно в двух направлениях: по вертикали или по горизонтали.
Вертикальное масштабирование повышает производительность, увеличивая объем памяти (чтобы нарастить существующие вычислительные ресурсы) или заменяя процессор на более мощный. В этом случае масштабируемость ограничена: очень сложно повысить скорость отдельного процессора, зато очень легко упереться в потолок производительности. Кроме того, переход на более мощные вычислительные ресурсы (например, покупка суперкомпьютера) обходится недешево, потому что вам приходится все больше платить за мощные облачные экземпляры или оборудование, получая при этом все меньше выигрыша.
Можно еще немного выгадать, если удастся сократить процессорное время, которое необходимо для той или иной единицы работы, но в конечном счете придется масштабировать систему по горизонтали. Горизонтальное масштабирование увеличивает производительность программ или систем за счет того, что нагрузка распределяется между существующими и новыми вычислительными ресурсами. Пока есть возможность добавлять новые ресурсы, можно наращивать производительность системы. В этом случае проблемы с масштабируемостью возникнут не так скоро, как при вертикальном масштабировании.
Компьютерная отрасль в основном тяготеет к горизонтальному масштабированию. Основные причины этого — потребность в системах реального времени, большие объемы данных, популярный подход «надежность через избыточность», а также оптимизированное потребление ресурсов за счет их совместного использования при переходе на облачные и SaaS-среды.
Горизонтальное масштабирование требует конкурентности на уровне системы, и одного компьютера может оказаться недостаточно. Комплексы соединенных машин — так называемые вычислительные кластеры — решают задачи обработки данных за разумное время.
У больших задач есть еще один важный аспект — сложность. К сожалению, со временем системы не становятся менее сложными, если только разработчики не прилагают специальных усилий. Компании хотят, чтобы их продукты были более мощными и функциональными, а от этого неизбежно усложняется и кодовая база, и инфраструктура, и работа по сопровождению. Специалистам приходится искать и реализовывать разные архитектурные приемы, чтобы упрощать системы и делить их на более простые независимые единицы, которые взаимодействуют друг с другом.
В разработке ПО практически всегда приветствуется разделение обязанностей. Создавать слабо сцепленные системы позволяет основной инженерный принцип — «разделяй и властвуй». Если группировать логически и функционально связанные фрагменты кода (компоненты с сильным сцеплением, tightly coupled) и разделять не связанные фрагменты (компоненты со слабым сцеплением, loosely coupled), то приложения становится проще понимать и тестировать, а количество ошибок сокращается — по крайней мере теоретически.
Так что конкурентность можно рассматривать и как стратегию ослабления сцепления. Если разделять функциональность между модулями или единицами конкурентности, то отдельные части смогут лучше сосредоточиться на конкретной функциональности, их будет проще сопровождать, а общая сложность системы уменьшится. Разработчики отделяют то, что нужно сделать, от того, когда это будет сделано. Это кардинально улучшает производительность, масштабируемость, надежность и внутреннюю структуру приложения.
Конкурентность играет важную роль и широко используется в современных вычислительных системах, операционных системах и больших распределенных кластерах. Она помогает моделировать реальный мир, максимизирует эффективность систем с точки зрения пользователей и разработчиков, а также позволяет разработчикам решать большие и сложные задачи.
Путешествие по миру конкурентности изменит ваши представления о компьютерных системах и их возможностях. Посмотрим на то, как устроен этот мир с точки зрения разных уровней конкурентности.
Как и многие сложные концепции проектирования, конкурентность строится на нескольких уровнях. В многоуровневой архитектуре важно понимать, что структуры, которые на первый взгляд противоречат друг другу или взаимно исключают друг друга, могут конкурентно сосуществовать на разных уровнях. Например, ничто не мешает организовать конкурентное выполнение на последовательной машине.
Мне нравится представлять многоуровневую архитектуру конкурентности как симфонический оркестр, который играет, допустим, Чайковского:
• На самом верху располагается концептуальный уровень, или уровень проектирования (прикладной уровень). Его можно сравнить с нотами, которые композитор написал для оркестра. В компьютерной системе роль такой партитуры играют алгоритмы, которые указывают компонентам системы, что им делать.
• Далее идет механизм многозадачности в среде выполнения (уровень среды выполнения). Он подобен музыкантам, которые совместно играют разные партии одного и того же произведения на разных инструментах. Музыкальное исполнение переходит от одной группы инструментов к другой в соответствии с жестами дирижера. В компьютерной системе этот уровень представлен разными процессами, каждый из которых выполняет свою часть работы в рамках общей цели.
• Наконец, мы приходим к низкоуровневому исполнению кода (аппаратный уровень). Здесь мы детализируем исполнение до уровня конкретных инструментов, например скрипки. Каждая нота, которую играет скрипач, возникает в результате того, что от одной до четырех струн колеблются с определенной частотой, которая зависит от длины, диаметра, натяжения и плотности струны. В компьютерной системе каждый процесс выполняет задачи в соответствии с инструкциями, которые относятся к этому конкретному процессу.
Каждый уровень описывает один и тот же процесс с разной степенью детализации, но подробности описания различаются, а иногда и противоречат друг другу.
То же самое происходит с конкурентностью:
• На аппаратном уровне мы имеем дело непосредственно с машинными командами: их выполняют вычислительные ресурсы, которые с помощью сигналов обращаются к периферийным устройствам. Современные архитектуры становятся все сложнее, и поэтому, чтобы оптимизировать производительность приложений в таких архитектурах, нужно глубоко разбираться в том, как приложения взаимодействуют с аппаратными компонентами.
• При переходе на уровень среды выполнения многие высокоуровневые сущности, связанные с программными абстракциями, скрываются за таинственными системными вызовами, драйверами устройств и алгоритмами планирования, которые существенно влияют на конкурентные системы, — а значит, в них тоже нужно глубоко разбираться. Этот уровень часто представлен операционной системой, и мы поговорим об этом подробнее в главе 3.
• Наконец, на прикладном уровне становятся доступными абстракции, которые по духу ближе к тому, как устроен физический мир. Программисты пишут исходный код, который реализует сложные алгоритмы и отражает бизнес-логику. Кроме того, этот код может влиять на порядок выполнения при помощи специальных языковых средств; в общем случае он представляет очень абстрактные сущности, которые понятны только разработчикам.
Далее мы будем широко использовать эти уровни как ориентиры в нашем восхождении к вершинам мастерства в области конкурентности.
Конкурентность заслужила репутацию непростой дисциплины. Ее сложность отчасти связана с тем, что по этой теме не хватает литературы, которую написали бы опытные практикующие специалисты. Сфера конкурентности больше опирается на устные традиции, чем на знания, закрепленные в письменном виде, и поэтому она до сих пор окутана тайнами. Я написал эту книгу, чтобы хоть немного приподнять завесу над этими тайнами.
Не стоит ожидать, что эта книга научит всему, что вам когда-либо понадобится знать о конкурентности. Она всего лишь поможет сделать первые шаги и понять, какие темы придется изучать подробнее. Мы рассмотрим некоторые задачи из области конкурентного программирования и опишем передовые методы, которые пригодятся, чтобы создавать конкурентные и масштабируемые приложения.
Программисты начального и среднего уровня получат базовые представления о том, как разрабатывать конкурентные системы. Чтобы извлечь максимум пользы из материала, желательно иметь некоторый опыт программирования, но вовсе не обязательно быть экспертом. Мы будем сперва объяснять ключевые понятия в общих чертах на конкретных примерах, а затем демонстрировать их в действии на языке программирования Python.
Книга делится на три части, в которых рассматриваются разные уровни конкурентности. Первая часть посвящена фундаментальным понятиям и примитивам написания конкурентных программ; она охватывает все уровни — от аппаратного до прикладного.
Тема второй части — проектирование конкурентных приложений и популярные паттерны конкурентности. В этой части также рассказывается о том, как преодолевать распространенные проблемы, которые возникают при разработке конкурентных систем.
Третья часть книги расширит ваши знания в области конкурентности: мы не ограничимся одной машиной, а будем масштабировать приложения на несколько машин, которые связаны по сети. Мы рассмотрим асинхронную коммуникацию между задачами — тему, которая чрезвычайно важна в этом контексте. Кроме того, здесь вы найдете пошаговое руководство о том, как писать конкурентные приложения.
К концу книги вы возьмете уверенный курс к вершинам мастерства в области конкурентности и будете ориентироваться в современных методах асинхронного и конкурентного программирования. Мы пройдем путь от низкоуровневых аппаратных операций до высокоуровневого проектирования приложений и переведем теорию на язык практической реализации.
Весь код в книге написан на языке Python 3.9 и протестирован в операционных системах macOS и Linux. Материал не привязан ни к какому конкретному языку программирования, хотя опирается на подсистему ядра Linux. Весь исходный код примеров доступен в репозитории GitHub (https://github.com/luminousmen/grokking_concurrency) и на сайте книги (www.manning.com/books/grokking-concurrency).
• Конкурентная система — это система, которая способна работать сразу с несколькими задачами.
• В реальном мире многие события происходят одновременно. Если вы хотите моделировать реальный мир, вам понадобится конкурентное программирование.
• Конкурентность радикально улучшает производительность и пропускную способность системы, потому что при этом задержка сокращается или скрывается, а существующие ресурсы эксплуатируются эффективнее.
• В книге используются понятия масштабируемости и ослабления сцепления:
• Масштабируемость бывает вертикальной или горизонтальной. Вертикальное масштабирование повышает производительность за счет обновления существующих вычислительных мощностей. Горизонтальное масштабирование повышает производительность за счет того, что нагрузка распределяется между существующими и новыми вычислительными ресурсами. Архитектуры компьютерных систем в основном тяготеют к горизонтальному масштабированию, при котором обязательно задействуется конкурентность.
• Сложные задачи можно разделить на простые компоненты, связанные друг с другом. В определенном смысле конкурентность — это стратегия ослабления сцепления, которая помогает решать сложные и большие задачи.
• Отправляясь в незнакомое место, обычно стоит захватить с собой карту, которая поможет вам не заблудиться. В этой книге нашим ориентиром станут уровни конкурентности: прикладной уровень, уровень системы выполнения и аппаратный уровень.
1 Herb Sutter. «The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software», http://www.gotw.ca/publications/concurrency-ddj.htm.
В этой главе
• Терминология, относящаяся к выполнению программ
• Низший уровень конкурентности: различные подходы к физическому выполнению задач
• Наброски нашей первой параллельной программы
• Ограничения технологии параллельных вычислений
Тысячелетиями (ладно, это небольшое преувеличение, — но уже очень давно) разработчики писали программы, используя простейшую модель вычислений — последовательную. Последовательное выполнение лежит в основе последовательного программирования, которое станет отправной точкой нашего знакомства с конкурентностью. В этой главе я представлю разные схемы выполнения, которые относятся к самому низкому уровню выполнения.
Первая проблема с конкурентностью (и вообще c computer science) заключается в том, что нам очень плохо удается присваивать имена. Иногда одно слово обозначает несколько разных понятий, иногда разные слова обозначают одно и то же, а иногда разные слова соответствуют разным понятиям, но их смысл зависит от контекста. А бывает, что мы просто выдумываем новые слова.
Примечание А вы знали, что CAPTCHA — заумный акроним для «Completely Automated Public Turing test to tell Computers and Humans Apart», то есть «Полностью автоматизированный общедоступный тест Тьюринга для различения компьютеров и людей»?
Таким образом, прежде чем разговаривать о процессе выполнения, имеет смысл разобраться, что же конкретно выполняется, и задать общую терминологию, которая будет использоваться в книге. В общем случае программа — это последовательность команд, которую выполняет компьютерная система.
Чтобы программу можно было выполнять, ее сначала нужно написать. Для этого пишут исходный код