По сравнению с этим асинхронное программирование превосходит синхронное как по потреблению памяти, так и по производительности.
Этот факт нам известен уже много лет. Если вы посмотрите на Django или Ruby on Rails, возможно, две наиболее многообещающие веб-фреймворки, появившиеся за последние несколько лет, то оба они написаны с учетом синхронного стиля.
Почему даже в 2010 году мы пишем программы, основанные на синхронном программировании? Причина, по которой мы застряли в синхронном программировании, двоякая.
Во-первых, способ написания кода непосредственно для асинхронного поведения неудобен.
Во-вторых, популярные и/или распространённые языки не имеют достаточного количества встроенных конструкций, необходимых для реализации менее простых подходов к асинхронному программированию.
Асинхронное программирование слишком сложно
Давайте сначала посмотрим на простую реализацию: цикл событий.При таком подходе мы имеем один процесс с замкнутым бесконечным циклом.
Функциональность достигается за счет быстрого выполнения небольших задач в этом цикле.
Одна из них может читать несколько байтов из сокета, другая функция может записывать несколько байтов в файл, а третья может выполнять некоторые вычисления, например выполнять операцию XOR для данных, буферизованных из первого сокета.
Самое важное в этом цикле то, что одновременно выполняется одна и только одна задача.
Это означает, что вам придется разбить логику на небольшие части, которые выполняются последовательно.
А если одна из функций блокируется, то она останавливает весь цикл и в этот момент ничего нельзя выполнить.
У нас есть несколько действительно хороших фреймворков, призванных упростить работу с циклами событий.
В Python это Twiste и, несколько более новый, Tornado. В Ruby есть EventMachine. В Perl есть POE. Эти платформы выполняют двойную функцию: они предоставляют конструкции, упрощающие работу с циклом событий (например, отсрочки или обещания), и обеспечивают асинхронную реализацию общих задач, таких как клиенты HTTP или DNS. Но эти фреймворки не очень хороши для асинхронного программирования по двум причинам.
Во-первых, нам следует изменить способ написания кода.
Представьте, как бы выглядела простая страница блога с комментариями.
Вот небольшой JavaScript, чтобы показать, как он работает в синхронной среде:
А теперь фрагмент кода, демонстрирующий, как это можно сделать в асинхронной среде.function handleBlogPostRequest(request, response, postSlug) { var db = new DBClient(); var post = db.getBlogPost(postSlug); var comments = db.getComments(post.id); var html = template.render('blog/post.html', {'post': post, 'comments': comments}); response.write(html); response.close(); }
Сразу стоит отметить несколько вещей: код специально написан так, что не требуется 4 уровня вложенности.
Мы также написали обратные вызовы внутри handleBlogPostRequest, чтобы воспользоваться преимуществами замыканий, такими как доступ к объектам запроса и ответа, контексту шаблона и клиенту базы данных.
При написании такого кода нам следует подумать о том, как избежать вложенности и замыкания.
Но в синхронной версии этого даже не подразумевается.
function handleBlogPostRequest(request, response, postSlug) {
var context = {};
var db = new DBClient();
function pageRendered(html) {
response.write(html);
response.close();
}
function gotComments(comments) {
context['comments'] = comments;
template.render('blog/post.html', context).
addCallback(pageRendered); } function gotBlogPost(post) { context['post'] = post; db.getComments(post.id).
addCallback(gotComments); } db.getBlogPost(postSlug).
addCallback(gotBlogPost);
}
Кстати, я выбрал JavaScript, чтобы подчеркнуть свою точку зрения.
Люди сейчас очень счастливы узел.
js , и это очень крутой фреймворк, но он не скрывает всей сложности, связанной с асинхронностью.
Он скрывает лишь некоторые детали реализации цикла событий.
Вторая причина, почему эти фреймворки недостаточно хороши, заключается в том, что не все операции ввода-вывода могут быть правильно обработаны на уровне фреймворка, и в этом случае приходится прибегать к хакам.
Например, MySQL не предоставляет асинхронные драйверы, поэтому большинство известных платформ используют потоки, чтобы гарантировать, что эта связь будет работать «из коробки».
Получающийся в результате неуклюжий API, дополнительная сложность и тот простой факт, что большинство разработчиков не меняют свой стиль кодирования, приводят нас к выводу, что этот тип фреймворка не является желаемым окончательным решением проблемы (я бы сказал, что вы могли бы выполнить настоящую работу сегодня, используя эти методы, как это уже сделали многие программисты).
Это заставляет нас задаться вопросом: какие еще варианты асинхронного программирования у нас есть? Сопрограммы и облегченные процессы, что подводит нас к новой важной проблеме.
Языки не поддерживают более легкие асинхронные парадигмы
Существует несколько языковых конструкций, которые при правильной реализации в современных языках могут проложить путь к альтернативным методам асинхронной записи, избегая при этом недостатков цикла событий.Этими конструкциями являются сопрограммы и облегченные процессы.
Сопрограмма — это функция, которая может останавливаться и возвращаться к выполнению в определенном, программно указанном месте.
Эта простая концепция может позволить преобразовать код, выглядящий как блокирующий, в неблокирующий код. В нескольких критических точках кода библиотеки ввода-вывода низкоуровневые функции, выполняющие ввод-вывод, могут решить сотрудничать.
В этом случае один может приостановить выполнение, а другой возобновить выполнение и так далее.
Вот пример (на Python, но думаю понятен): def download_pages():
google = urlopen(' http://www.google.com/ ').
read() yahoo = urlopen(' http://www.yahoo.com/ ').
read()
Обычно это работает следующим образом: открывается новый сокет, устанавливается соединение с Google, отправляется HTTP-заголовок, полный ответ считывается, буферизуется и присваивается переменной.
Google .
Затем сделайте то же самое для переменной Yahoo .
Хорошо, теперь представьте, что базовая реализация сокета была построена с использованием сопрограмм, которые взаимодействуют друг с другом.
На этот раз, как и в прошлый раз, сокет будет открыт и установлено соединение с Google, после чего будет отправлен запрос.
Но на этот раз после отправки запроса реализация сокета приостановит его выполнение.
Приостановив свое выполнение (но еще не вернув значение), выполнение продолжится со следующей строки.
То же самое происходит и со строкой Yahoo: как только запрос отправлен, линия Yahoo приостанавливает выполнение.
Но всё равно есть с чем взаимодействовать — например, некоторые данные могут быть прочитаны из сокета Google — и в этот момент он возвращается к своему выполнению.
Он считывает некоторые данные из сокета Google и снова делает паузу.
Выполнение переключается между двумя сопрограммами, пока одна из них не завершится.
Например, сокет Yahoo прекратил работу, а Google — нет. В этом случае сокет Google продолжает читать свой сокет до завершения, поскольку нет других сопрограмм для связи.
Как только сокет Google будет окончательно завершен, функция вернет весь буфер.
Тогда строка от Yahoo вернет все свои данные.
Мы сохранили стиль нашего блокирующего кода, но использовали асинхронное программирование.
Самое замечательное, что мы придерживаемся исходного алгоритма программы — сначала присваивается переменная.
Google затем Yahoo .
По правде говоря, где-то внизу у нас есть умный цикл событий, определяющий, кто получит исполнение, но это скрыто от нас фактом использования сопрограмм.
Такие языки, как PHP, Python, Ruby, Perl, просто не имеют встроенных сопрограмм, достаточно быстрых для реализации такого преобразования в фоновом режиме.
А как насчет облегченных процессов? Облегченные процессы — это то, что Erlang использует в качестве основного многопоточного примитива.
По сути, эти процессы в основном реализованы в виртуальной машине Erlang. Каждый процесс имеет около 300 слов служебных данных и планируется в первую очередь в виртуальной машине Erlang без разделения состояния между всеми процессами.
По сути, нам не нужно думать о создании процесса, это практически бесплатно.
Хитрость в том, что все процессы могут взаимодействовать только посредством передачи сообщений.
Использование облегченных процессов на уровне виртуальной машины устраняет чрезмерное потребление памяти, конкурсные изменения и относительную медлительность межпроцессного взаимодействия, обеспечиваемого операционной системой.
Одна и та же виртуальная машина имеет полный доступ к стеку памяти каждого процесса и может свободно перемещать или изменять размер этих процессов и их стеков.
Это то, чего ОС просто не может сделать.
Благодаря этой облегченной модели процессов можно вернуться к общей модели использования различных процессов для всех наших асинхронных нужд. Возникает вопрос: можно ли реализовать концепцию облегченного процесса на языках, отличных от Erlang? Ответ: «Не знаю».
На мой взгляд, Erlang использует некоторые особенности языка (например, отсутствие изменяющихся структур данных - примечание редактора: переменных нет) для выпуска легковесных процессов.
И куда идти дальше?
Основная идея заключается в том, что разработчики должны думать о своем коде с точки зрения обратных вызовов и асинхронности, как того требуют асинхронные платформы, основанные на циклах событий.Спустя 10 лет мы по-прежнему видим, что большинство разработчиков, столкнувшихся с этой проблемой, просто игнорируют ее.
Они продолжают использовать старые надоедливые методы блокировки.
Нам следует рассмотреть альтернативные реализации, такие как сопрограммы и облегченные процессы, которые позволят нам сделать асинхронное программирование таким же простым, как синхронное.
Только тогда мы сможем избавиться от нашей привязанности к синхронности.
Примечание пер.
: Между тем, сопрограммы уже активно используются.
По крайней мере, в питоне:
- www.python.org/dev/peps/pep-0342
- www.dabeaz.com/coroutines/Coroutines.pdf - супер материал
- Softwaremaniacs.org/blog/2010/09/18/ijson
-
Как Продаются Стартапы В Кремниевой Долине
19 Oct, 24 -
Как Я Научился Программировать После 30
19 Oct, 24 -
Спорт Для Программиста Второй Свежести
19 Oct, 24 -
Проектирование И Демпинг
19 Oct, 24