Пользователь
0,1
рейтинг
14 октября 2010 в 05:04

Разработка → Как избавиться от пристрастия к синхронности перевод

При сравнении асинхронное программирование превосходит синхронное, как по потреблению памяти, так и по производительности. Мы знакомы с этим фактом уже годы. Если посмотреть на Django или Ruby on Rails, возможно два самых многообещающих веб-фреймворка, появившихся за последние несколько лет, оба написаны из расчета на синхронный стиль. Почему даже в 2010 году мы пишем программы, полагающиеся на синхронное программирование?

Причина, по которой мы застряли в синхронном программировании, двоякая. Во-первых, манера написания кода непосредственно для асинхронного поведения неудобна. Во-вторых, популярные и/или распространенные языки имеют недостаточно встроенных конструкций, требующихся для реализации менее прямолинейных походов к асинхронному программированию.

Асинхронное программирование слишком сложно



Давайте сначала рассмотрим прямую реализацию: цикл обработки событий. При этом походе мы имеем один процесс с замкнутым бесконечным циклом. Функциональность достигается быстрым выполнением маленьких задач в этом цикле. Одна из них может считывать несколько байт из сокета, пока другая функция может писать несколько байт в файл, а еще одна может делать какие-нибудь вычисления, например считать XOR на данных, которые буффиризированы из первого сокета.

Самое важное в этом цикле — то, что одна и только одна задача выполняется в каждый момент времени. Это значит, что вам придется разбивать логику на маленькие кусочки, которые выполняются последовательно. И если одна из функций блокируется, это останавливает весь цикл, и ничего не может выполняться в этот момент.

У нас есть несколько действительно хороших фреймворков, нацеленных на то, чтобы сделать работу с циклами обработки событий проще. В Python это Twisteв, и, несколько новее, Tornado. В Ruby есть EventMachine. В Perl есть POE. То, что эти фреймворки делают двояко: предоставляют конструкции для более простой работы с циклом событий (как например, Задержки (Deferreds) или Обещания (Promises), и предоставляют асинхронные реализации обычных задач, к примеру, 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 чтобы показать точку зрения. Люди сейчас очень довольны node.js, и это очень клевый фреймворк, но он не скрывает всей сложности тянущейся за асинхронностью. Он только прячет некоторые детали реализации цикла событий.

Вторая причина почему эти фреймворки не достаточно хороши — то, что не весь I/O может быть обработан должным образом на уровне фреймворка, и в этом случае надо обращаться к хакам. К примеру, MySQL не предоставляет асинхронных драйверов, так что большинство известных фреймворков используют нити (threads), чтобы быть уверенным, что эта коммуникация будет работать из коробки.

Полученный неудобный API, добавочная сложность, и простой факт, что большинство разработчиков не меняют свой стиль кодирования, приводит нас к заключению, что этот тип фреймвокров не есть желанное финальное решение проблемы (я допускаю мысль, что вы может выполнить Реальную Работу сегодня используя эти техники, как и уже многие программисты). Это приводит нас к размышлению: а какие другие варианты у нас есть для асинхронного программирования? Сопрограммы (сoroutines) и легковесные процессы, что приводит нас к новой важной проблеме.

Языки не поддерживают более легких асинхронных парадигм



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

Сопрограмма- это функция, которая может остановиться и вернуться к выполнению в определенным, программным образом заданном, месте. Это простая концепция может позволить преобразовать выглядящий блокирующим код в неблокирующий. В нескольких критических точках кода вашей I/O библиотеки, низкоурвневые функции, выполняющие I/O могут решить «скоорперироваться». В этом случае одна может приостановить выполнение, пока другая возвращается к выполнению, и так далее.

Вот пример (на Питоне, но я думаю понятно):
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 VM. Каждый процесс имеет примерно 300 слов избыточности(overhead) и его выполнние планируется, главным образом, в Erlang VM, не разделяя состояние между всеми процессами. По сути, нам не нужно задумываться о создании процесса, это практически бесплатно. Уловка в том что все процессы могут взаимодействовать только посредством передачи сообщений.

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

С этой моделью легковесных процессов возможно опять вернуться к общепринятой модели использования разных процессов для всех наших асинхронных нужд. Вопрос становится следующим: может ли понятие легковесного процесса быть реализовано на языках помимо Erlang? Ответ: «Я не знаю.» Как я считаю, Erlang использует некоторые особенности языка (такие, как отсутствие изменяющихся структур данных — Прим. ред.: в нём нет переменных) для своей релизации легковесных проецессов.

И куда же двигаться дальше



Ключевая идея заключается в том, что разработчики должны думать про свой код в терминах коллбеков и асинхронности, как от них требуют асинхронные, базирующиеся на цикле событий, фреймворки. По прошествии 10 лет, мы по-прежнему видим, что большинство разработчиков, поставленных перед этим вопросом просто игнорируют его. Они продолжают использовать раздражающие блокирующие методологии былого.

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

Прим. пер.: Между тем, сопрограммы уже активно используются. По крайней мере в питоне:
Перевод: Eric Florenzano
Владимир Абрамов @kivsiak
карма
13,9
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +1
    Корутины? А как бы вы перевели routines? А coworkers?
    • 0
      Хороший вопрос — я не нашел пока адекватного перевода. В немногочисленных русскоязычных статьях на эту тему используется термин «корутин». Если вы предложите адекватный русскоязычный термин, то я большим удовольствием отредактирую перевод.
      • +15
        «Сопрограмма». Используется, в частности, в русском переводе «Искусства программирования» Кнута.
        • +1
          А слона я и не заметил Ж) fixed
        • –4
          Я «корутины» не увидел, но прочитав про «сопрограммы», метнулся посмотреть, не Мицгол ли автор.
      • +1
        Самое интересное, что предложенный перевод есть и в гугле, и в лингво
        • +9
          А на Мультитране два. Второй — «копрограмма». Рыдаю до сих пор. ))
          • 0
            а что, звучит)
  • +1
    «Django или Ruby on Rails, возможно два самых многообещающих веб-фреймворка, появившихся за последние два года» — тут какая-то ошибка; точно не 2 года.
    • +1
      Rails с 2004-го вроде, про джангу не знаю
    • +5
      Да вообще такое ощущение, что статья года из 2005 )
      Тогда как раз не было еще Stackless python с зелеными тредами, не существовало гринлетов, не были написаны фреймворки которые прозрачно делают ввод-вывод асинхронным, не было PyPy со встроенными зелеными тредами.

      А сейчас, привет 2010 — автор понял что зеленые треды это хорошо.
      • +1
        А так же в перле есть Coro стабильной версии уже 6 лет. С большим набором дополнительных модулей.
      • 0
        И уж вообще ничего не сказано про Scala с её Actors и фреймворком lift.

        Что непростительно для темы, потому что это именно то движение вперёд, за которое выступает автор.
    • 0
      Да, спасибо. Ошибка в переводе. А статья от февраля 2010.
  • +15
    Основная проблема в том, что для написания программ в асинхронном стиле надо мыслить по другому.
    Люди с трудом усваивают, что нельзя сделать var foo = ajax('example.com');, а уж целое асинхроноое приложение — это просто ужас.
    Еще многие люди считают, что раз это (например) ДжаваСкрипт и несинхронно, то ничего кроме процедурщины уже не подходит. Немножко иначе выглядя все те же принципы остаются.
    Бороться с вложеностью можно стандартными для синхронного программирования способами, например ООП. Если нам надо получить отдельным запросом топик и отдельным — комменты, то можно очень красиво сделать это так:

    // обработка у нас будет выглядеть как-то так:
    new Topic(id).get(function (data) {
      render(data.article, data.comments);
    });
    
    // Сам класс прост:
    var Topic = function (conn, id) {
      this.conn = conn;
      this.id   = id;
    };
    
    Topic.prototype = {
    
      // Получены и статья и комменты:
      get : function (fn) {
        var data = {};
        var set = function (key, value) {
          data[key] = value;
          if ('article' in data && 'comments' in data) fn(data);
        };
        this.getArticle(function (article) {
          set('article', article);
        });
        this.getComments(function (comments) {
          set('comments', comments);
        });
      },
    
      // получение статьи из БД:
      getArticle(fn) {
        this.conn.query(qArticles, function (err, res) {
          if (err) throw err;
    
          res.fetchAll(function (err, rows) {
            if (err) throw err;
            fn(rows.length ? rows[0] || null);
          });
        });
      },
    
      // Получение комментов из БД:
      getComments(fn) {
        this.conn.query(qComments, function (err, res) {
          if (err) throw err;
    
          res.fetchAll(function (err, rows) {
            if (err) throw err;
            fn(rows);
          });
        });
      },
    };
    


    Методы разбиты на небольшие куски, красиво сгруппированы, вложенность — минимальная. Код читается не лучше, чем аналогичный синхронный.
    • 0
      «Код читается не лучше, чем» => «Код читается не хуже, чем»
      • –1
        Еще бы if (err) throw err;
        писать как
        this.err(err) — имхо читаемее будет
        • 0
          согласен. с другой стороны, при логировании будет указывать не на ту строку.
      • 0
        внимательный читатель может заметить еще пару опечаток, но на идею они не влияют
        • 0
          Одну нашел:

          rows.length? rows[0] || null

          Надо так:

          rows.length? rows[0]: null
          • 0
            еще две критических есть как минимум)
    • +2
      Огромнешее спасибо за пример!
      • 0
        незачто)
    • 0
      Только ошибки лучше отдавать в колбэк, а не делать throw. Иначе их не поймать будет уровнем выше.
  • 0
    Программисты меня, конечно, не поддержат, оцениваю как менеджер:
    что дешевле: поддерживать сложный код (это требует больших временных затрат на написание и тестирование и сложнее к тому же, новички-программисты могут не справиться, а значит, такое решение будет дороже + оно и не обкатанное ещё нигде особо) или просто купить ещё несколько серверов в hetzner? :-)

    Мне кажется, что такое будет эффективно только на проектах масштаба яндекса, твиттера, вконтакте и подобных :-)
    Для обычных сайтов экономически целесообразнее использовать те технологии, что есть сейчас… Потому что проще…
    • 0
      На мелких задачах оба подхода будут приблизительно однаково быстро программироваться и приблизительно одинаково быстро выполнятся.
      На крупных задачах оба подхода будут долго програмится, но асинхронный будет быстрее работать
      Плюс дело в опыте
    • 0
      На мелких задачах, да )) jQuery ну ооочень синхронный.
    • +2
      Ха-ха! Как раз в тему твит Джоела Спольски:
      "@spolsky: Digg: 200MM page views, 500 servers. Stack Overflow: 60MM page views, 5 servers. What am I missing? "
  • 0
    Ссылка в тексте на node.js, это куда?
    • 0
      Видимо очепятка, должно быть так
  • 0
    Вы забыли про твистед в питоне — очень замечательный пример, я тоже сначало смотрел с круглыми глазами на него.
    • +1
      он там упомянут вначале, просто называется почему-то Twister )
      • 0
        Наверное автор только из Ростикса вернулся.
  • –1
    «Давайте, наконец, слезем с кресел-каталок, и пойдем с костылями! Да, на кресле конечно больше удобств, зато с костылями больше шансов по настоящему...» — тут подставьте своё
  • –1
    Почему даже в 2010 году мы пишем программы программы, полагающиеся на синхронное программирование?
    Повторяется слово «программы».
  • –6
    http:/tsya.ru
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      В насквозь асинхронном Эрланге (который, на секундочкУ, с 1992-го года) тулза для отладки распределенных/асинхронных приложений только-только появилась. Так что да, инструментарий для асинхронности — это пока утопия.
  • 0
    хм. а по мне синхронный код проще.
    помню разбирались как-то с одним асинхронным проектом. проблема была с разной производительностью у модулей проекта.
    Примерно так — из сокета принимается сообщение, для него выделяется память и этот объект ставится в очередь. Другой модуль выбирал из этой очереди и что-то рисовал на экране (и довольно медленно). Получалось, что на быстрых сетях очередь сообщений могла расти в пиках очень быстро, а разгружалась медленно. Появлялся эффект «резиновой отвертки» — отображение информации запаздывает несмотря на жуткий трафик.
    В синхронной системе происходит автоматическая регулирование — прием данных в темпе рисования, и не более того.

    Хотя конечно и асинхронный и синхронный код всегда можно написать плохо.
    • +1
      Полагаю в таком случае имело смысл совмещать синхронный и асинхронный подход?
      Ничто не мешает нам заменить часть асинхронного кода синхронным? Ну например, заставив после асинхронного вызова ожидать программу.
      • 0
        А вообще, неплохо заранее продумывать архитектуру в таких задачах.
        По-моему, хорошие асинхронные системы можно проектировать, только когда словил достаточно граблей и понабрался большого опыта. Короче, лет через 10 коммерческого программирования асинхронность начинает подразумеваться автоматически.
    • +1
      Совет автору: а вы абсолютно все пакеты пришедших данных пытались там выводить на графике? Очередь сообщений надо бы разгружать сразу: если вам нужны данные «на потом», то лучше сразу подсчитать среднее значение (которое пойдёт на график).
      Вообще, задача очень нетривиальная, см. TCP throttling.

      И, простите, «в лоб» не решаемая — получили то, что получили :-)
  • 0
    В питоне нет twister'a, есть twisted
  • +5
    Не Twister, а Twisted. И он тормоз.

    Не раскрыта тема Stackless Python. Erlang, Ocaml/Lwt, Haskell/ForkIO и т.п

    Есть примеры кода с «синхронным» подходом, но не вижу асинхронного кода.

    Так где про асинхронность?
  • +6
    На самом деле, любое многопоточное программирование — это complexity booster. Какие бы прекрасные возможности во фреймворке для него не предоставлялись, в реальности там все равно будут проблемы с race conditions, одновременным доступом и так далее.

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

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

    Что Фаулер, что Эспозито/Сальтарелло предостерегают от использования многопоточности без четкого предварительного планирования. Банальный пример: один цикл request/response на веб-сервере почти всегда можно и нужно писать синхронно (но при этом ориентироваться на stateless-модель, чтобы он легко масштабировался на любое количество выполнений); исключения составляют множественные длинные и независимые друг от друга операции (читать два файла параллельно, если они в одном хранилище, скорее всего, неопраданно; читать информацию с двух разных сайтов — скорее всего, имеет смысл).
    • НЛО прилетело и опубликовало эту надпись здесь
      • –2
        Асинхронности без многопоточности не бывает.

        Если вы ее не видите (т.е., для вас ее спрятала платформа), вам повезло. Но за кадром это всегда многопоточность. И чем больше выигрыша вы хотите получить от использования асинхронности, тем ближе к реальности (многопоточности) вам надо быть.
        • +5
          :-) ну, ну, ну…
          асинхронность без многопоточности — это один цикл и действия, разбитые во времени.
          Вполне себе бывает.
          И именно здесь есть среднее звено парадигмы мышления. Т.е., сначала мы пишем последовательный («синхронный» :-) код. Затем учимся разбивать код на независимые от последовательности выполнения группы («асинхронный» код). А потом уже работаем с многопоточной обработкой — но опыт с асинхронным кодом идёт нам на пользу.
          • 0
            Возможно подразумевают эмуляцию асинхронности через многопоточность например в драйвере MySQL для библиотек написанных в синхронном стиле. А так да, все в одном потоке.

            Кстати, как асинхронные задачи работают на многопроцессорных машинах?
            • 0
              Как-как…

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

              Или так (если не о питоне речь, где GIL путает карты): один процесс работает в асинхронном режиме (принимает запросы ), обработку же отправляет в отдельные треды на соседние ядра/процессоры.
          • 0
            «асинхронность без многопоточности — это один цикл и действия, разбитые во времени.»
            А можно пример для тупых?
            • 0
              Ну почему же для тупых? Я, наверное, неясно выразился просто.
              Цикл, как правило, подразумевает наличие некого фреймворка (возможно, и самописного), в котором этот самый цикл обычно вертится.
              Т.е., вместо операций «скачать» пишем операции «начать качать» и «обрабатывать в таком то-месте, когда скачается». Фреймворк же внутри может «poll'ить» сетку на предмет входящих данных.
              Проще GUI'ёвый пример, когда нужно развязать GUI, логику и обработку. Одного потока часто будет достаточно, если обработка разбита на маленькие куски, каждый из которых делает маленькую часть работы, а в промежутках идёт работа с GUI.
              Цикл, коллбэки, маленькие операции — думаю, этого достаточно для реализации асинхронности без многопоточности.
              • –1
                «Одного потока часто будет достаточно, если обработка разбита на маленькие куски, каждый из которых делает маленькую часть работы, а в промежутках идёт работа с GUI.»
                Угу. Попробуйте «разбить на маленькие куски» обращение в БД.
                • +1
                  При желании можно и нужно — вопрос в том, чем программа должна заниматься, пока идёт SQL-запрос. Если нужно делать что-то ещё, или сохранить отзывчивость GUI'я — то нужно, чтобы интерфейс доступа к БД поддерживал асинхронные запросы. Обычно так оно и есть. Так в чём же проблема (или задача)?
                  • 0
                    «нужно, чтобы интерфейс доступа к БД поддерживал асинхронные запросы.»
                    Угу. Где они выполняются? Неужели в том же потоке, что и ваш код?
                    • 0
                      Можно и в том же потоке, если у вас что-нибудь вроде SQLite. Можно и жёсткий диск читать асинхронно, по кусочку заполняя оперативку. Вот представьте, не было бы у вас операционки, не было бы готового движка БД. Например, при программировании какого-нибудь вычурного PLC-контроллера, в котором даже прерываний нет, и флеш-память можно читать непосредственно процессором, безо всякого там DMA и т.п.
                      Можно там асинхронный код написать, да, только он и будет нормально работать.
                      Прошу прощения за такой сверхъестественный пример.
                      • –1
                        «вот представьте, не было бы у вас операционки»
                        Неинтересно. Я уже снизу написал, что можной сделать полностью свой шедулер процессорного времени, только он будет синхронный неизбежно.

                        Как бы, вопрос терминологии.

                        Асинхронность — это именно возможность плюнуть куда-то _длинную_ задачу и не думать о том, как дробить ее на короткие.
                        • 0
                          Если понимать асинхронность именно так, то, да, без многопоточности не обойтись. Но, боюсь, вы запутались в терминологии — хотя мне безразлично, как называть вещи (ну, не обязательно же своими именами, правда? :-)
                          Предлагаю перестать здесь флудить и пообщаться в личке.
                    • 0
                      Под однопоточностью имеется в виду, что ваш асинхронный код никогда не будет выполнятся в нескольких потоках. Сторонние библиотеки, разумеется, в другом потоке работают.
                      • +2
                        «Сторонние библиотеки, разумеется, в другом потоке работают. „
                        Возвращаемся к началу: асинхронности без многопоточности не вышло.
            • 0
              Два асинхронных бесконечных цикла.

              (function() {
                  alert(1);
                  setTimeout(arguments.callee, 5000);
              })();
              
              (function() {
                  alert(2);
                  setTimeout(arguments.callee, 3000);
              })();
              


              • 0
                А зачем оно (помимо анноя бедного пользователя, загрузившего этот код)?
                • 0
                  alert, конечно, незачем, это пример. На практике какое-нибудь значение проверять, например. Или та же анимация.
                • 0
                  Ааа, понял. Я в JS не силён. Так понимаю, здесь движок JS сам будет вызывать эти безымянные функции.
              • 0
                Ага. Теперь скажите мне, кто обеспечивает работу функции setTimeout? Неужели правда тот же поток, в котором выполняется alert()?

                Впрочем, рекомендую задуматься не над таймаутами, а над вещами типа «получить ответ от сервера». Они тоже выполняются в том же потоке, что и js-код?

                Следующий шаг размышлений: преположим, у вас есть два реквеста, по завершению которых вы апдейтите два разных места на экране. Если реквесты завершились в одно и то же время, а апдейты идут долго, они будут идти параллельно или последовательно?
                • +2
                  Да, callback setTimeout'а выполняется в том же потоке, что и основной скрипт. Да, ответы от сервера тоже все выполняются в одном потоке строго по очереди. И да, места на экране тоже будут апдейтиться по очереди.

                  Именно поэтому при написании асинхронных скриптов (я про JS сейчас в основном, на других не писал) нельзя допускать, чтобы какой-то синхронный блок долго выполнялся, зависнет всё. В браузерном JS это легко наблюдается зависанием браузера, т.к. пока выполняется JS даже браузер ничего не делает, не то что какие-то другие потоки.
                  • +1
                    «Да, callback setTimeout'а выполняется в том же потоке, что и основной скрипт.»
                    Не коллбек. Само ожидание таймаута.

                    «Да, ответы от сервера тоже все выполняются в одном потоке строго по очереди.»
                    Не ответы от сервера, а _получение_ ответа.

                    Понимаете, я про это и говорю: у вас есть фреймворк (js-машина), которая дает вам асинхронность без необходимости явно работать с потоками. И она же дает вам сериализацию (строго последовательное выполнение) событий без вашего участия. Но единственный способ, которым это можно сделать (точнее, осмысленно делать на современных многопоточных ОС) — это несколько потоков. В одном работает браузер, в другом — компонент, отвечающий за http requests, и так далее. Иначе вы бы просто не смогли положить запрос к серверу на асинхронное выполнение и продолжить что-то делать.
                    • 0
                      Да, я уже понял, что вы пытаетесь доказать. Это всё понятно, с этим никто не спорит.
                      Профит-то в том, что вся эта многопоточность, она скрыта и отлажена, за неё волноваться не надо. Ваш же код выполняется всегда строго по очереди и никаких конфликтов, свойственных многопоточности, у него быть не может.
                      • +1
                        Только ваш собственный код не является асинхронным, он синхронизирован. Вы не можете написать длинную задачу в вашем собственном коде и выполнить ее асинхронно — она заблокирует другие задачи вашего собственного кода.
                    • НЛО прилетело и опубликовало эту надпись здесь
                      • 0
                        «После регистрации ЦПУ вообще не делает никаких действий, связанных с „_получение_ ответа“
                        То есть отсылка http request на сервер (на пару мегабайт), а потом получение оттуда ответа (тоже на пару мегабайт, кусочками) и складывание этого ответа в один объект, понятный js, происходит б-жественной силой?
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • +1
                            Вот именно про это я и говорю: _ваш код_ — синхронен. Вся асинхронность находится за его пределами.
                • 0
                  По поводу последнего с апдейтами — последовательно, кто первый — как карта ляжет. Речь же идет о javascript, верно?
              • 0
                На java так:
                Runnable task = new Runnable() {
                    public void run() {
                        while (true) {
                            try {
                                SwingUtils.invokeLater(new Runnable() {
                                    public void run() {
                                        JOptionPane.showMessageDialog(null, "1");
                                    }
                                });
                                Thread.sleep(5000);
                            } catch (InterruptedException ex) {
                                break;
                            }
                        }
                    }
                };
                new Thread(task).start();

                Выглядит страшновато, но это потому, что в Java нет анонимных функций, а есть анонимные классы. Ждём 7-й версии или пишем то же самое на С# и будет щасте. Тем более, что пример ужасно примитивный. На работе я строю тяжёлые отчёты примерно по такому же механизму. Во только task у меня реализует расширенный Runnable, который умеет уведомлять о состоянии генератора отчёта (прогресс + текущая выполняемая операция). А реализую я не непосредственно свой интерфейс, а свой же абстрактный класс, где определены protected методы reportProgress и reportActivity, в которых и дёргается SwingUtils.invokeLater. Так что получается, я пишу тот же последовательный код, в котором местами дёргаются reportProgress и reportActivity. А юзер не ждёт, пока программа выйдёт из цикла и начнёт реагировать, а видит гламурное окошко с прогрессбаром. Так что всем хорошо.

                Ну и наконец, замечу, что однажды мне пришлось писать веб-морду для некоего XML-RPC сервиса (нужно было просто последовательно дёрнуть 2-3 удалённых метода) и толстого клиента для него же. Так вот оказалось, что JS сильно проиграл в выразительности Java за счёт отсутствия поддержки многопоточности.
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Ваше объяснение мне понравилось. Просто и понятно.
              • +1
                Угу. Вот только сама программа, в которой работает event-loop, внутри синхронна. Асинхронна она только с точки зрения внешнего пользователя, который что-то положил в очередь, а через некое неизвестное время забрал. Беда в том, что чтобы это работало, выполнение пользователя не должно зависеть от выполнения очереди сообщений, а, значит, они сидят в разных потоках.

                (ну или у вас написан собственный шедулер задач, который выполняет ту же работу, что и шедулинг процессорного времени; все помнят, что на одноядерной машине многопоточность мнима?)
    • 0
      Э. Чуть докопаюсь к последнему абзацу. Вообще говоря, разве http-серверы — не редкая ли область, где с успехом используется асинхронная модель на неблокирующих сокетах? :) И часто — в симбиозе с многопоточной моделью?

      Как раз таки request/response очень просто реализовать.

      Сложности начинаются, когда появляется хитрый поток исполнения, с кучей условных ветвлений, сугубо синхронным кодом и так далее.
      • 0
        «Как раз таки request/response очень просто реализовать. „
        Вопрос только в том, зачем, если респонс все равно имеет смысл только целиком.

        Вы, собственно, путаете http-серверы, которые заведомо многопоточны, и приложения, которые на них крутятся. Вот вторые должны писаться максимально однопоточно.
        • 0
          Что значит «заведомо многопоточны»? tornado или twisted.web (или как там называется их сервер) вам в пример :) Скорее уж «традиционно многопоточны», но есть достаточно популярные альтернативы.

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

          • 0
            Да, я ошибся, «традиционно многопоточны».

            «Пока это все — размышления, реально делать ее никто не умеет, как верно заметил автор статьи.»
            А вот здесь вы повторяете мою ошибку. Почему никто не умеет? Платформы уже умеют. И некоторые люди, как следствие, тоже.
            • 0
              Платформы умеют — это хорошо. Кстати, а что вы имеете в виду под словом «платформа»?

              Я вижу за асинхронностью то же самое, что и за многопоточностью. Вот как-то так: «Можно использовать? Можно. Упрощает работу? Не факт. » Это должно быть приложение, написанное полностью в этом стиле, с учетом его особенностей и слабых мест.

              Скажем, сервер-сайд игрушки для Контакта/Фейсбука можно написать так, и получить выигрыш в потреблении ресурсов процессора, потому как бэкэнд обычно пишется с нуля. Вместо трех машин в связке достаточно будет одной-двух. Ура.

              Рядовой портальчик проще и надежнее наклепать на Джанге или вообще на чем-нибудь php-like.

              Когда же изменится ситуация в целом — не ясно, как не ясно, изменится ли она вообще.

              • 0
                «Платформы умеют — это хорошо. Кстати, а что вы имеете в виду под словом «платформа»?»
                Набор инструментов. Вот, скажем, .net — платформа. asp.net mvc — тоже платформа.

                Просто то будущее, которое для вас «однажды», для меня уже здесь и сейчас.
                • 0
                  Вы живете в платформах? :)

                  Ну, в этом-то смысле все платформы умеют в каком-то смысле асинхронность делать в том или ином виде. Те же сокеты неблокирующие уж сто лет в обед как работают, а с ними сам бог велел event loop наклепать.

                  А уже выше абстракции типа deferred или сопрограмм, или гринлетов, ну или чего там еще уже надумали за годы безделья :)

                  Вы лучше укажите, в какой именно сфере прикладной это вот прям ОЧЕНЬ-ОЧЕНЬ надо, чтоб я взял да и стал вдруг писать в этом стиле. Http-сервера мы уже обсуждали; не знаю как вам, а мне их писать часто не доводится :)
                  • 0
                    «Вы лучше укажите, в какой именно сфере прикладной это вот прям ОЧЕНЬ-ОЧЕНЬ надо, чтоб я взял да и стал вдруг писать в этом стиле.»
                    Все адекватные клиенты с удаленным сервером — чтобы не блокировать интерфейс на время обращения к серверу.

                    Более того, вообще любые клиенты с длительными операциями — в тех же целях.
                    • 0
                      И-и-и-и-и-и? И какие же клиенты в настоящий момент одновременно будут однозначно превосходить традиционную многопоточную/многопроцессную модель — и быть достаточно простыми в использвонии?

                      Достаточно простыми для того, чтобы не ТОЛЬКО веб-сервера однозначно удобней было бы писать в таком ключе? Потому как последние — достаточно простые системы, в которых, по существу отличается только узловой момент — прием запросов. В них не возникает типичных проблем асинхронного программирования в силу независимости запросов.
                      • 0
                        Стоп, а где я говорил, что асинхронность как-то противоречит многопоточности?

                        Ровно наоборот, с моей точки зрения, асинхронность — это частный случай реализации многопоточности, где некоторые вещи убраны «под капот».
                        • 0
                          Вы что-то путаете. Асинхронность — это асинхронность, многопоточность — это многопоточность.

                          Это перпендикулярные понятия. Иногда их используют вместе, иногда — порознь.

                          Под асинхронностью подразумеваются чаще всего некие неблокирующие вызовы. Скажем, в http-серверах это неблокирующее поток выполнения циклическое обращение к пулу событий на сокетах (select, poll или epoll в Линуксе).

                          Под многопоточностью — наличие в процессе нескольких параллельных потоков выполнения программы, имеющих доступ к общей памяти.

                          Над низкоуровневым асинхронным циклом событий иногда строят высокоуровневые примитивы: «легковесные» процессы, отложенные (deferred) обработчики и так далее.

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

                          Ну, мы отвлеклись. Я интересовался клиентами с длительными операциями, где асинхронный стиль дает преимущества.
                          • 0
                            «Вы что-то путаете. Асинхронность — это асинхронность, многопоточность — это многопоточность.»
                            Не путаю.

                            «Я утверждаю, что у всех этих примитивов есть некие проблемы, которые в достаточно большом количестве случае лишают подход однозначных преимуществ, а делает еще просто еще одним подходом.»
                            С этим я не спорю. Собственно, это и есть моя позиция.

                            «Я интересовался клиентами с длительными операциями, где асинхронный стиль дает преимущества. „
                            Банальный пример: вызов вебсервиса (не важно, откуда). В асинхронной модели я делаю вызов, указываю колбек — он будет вызван по завершению вызова, далее будет обработчик. В многопоточной модели мне надо будет положить синхронный вызов в отдельный поток и выполнить действия по его завершению. Лично для меня первое прозрачнее (еще и потому, что от меня скрывается то, на каких именно примитивах платформа реализует ожидание завершения вызова). Более того, именно первая модель позволяет сделать то, за что так хвалят js выше: всякие дополнительные упрощения в обработке коллбеков, например принудительную их сериализацию, которая убирает необходимость использования примитивов синхронизации внутри колбека.
                            • 0
                              «Не путаю» — эт что было, аргумент такой? :)

                              Ну давайте дальше пойдем по предложенному вами пути. Дернуть простой колбэк по событию. Дернуть другой колбэк по другому. Третий — по третьему. Им надо координировать действия или обменяться информацией. Как будете действовать?

                              Помнится, в js у меня иной раз довольно мутный код получался, нетривиальные решения приходилось принимать для сохранения читаемости кода. И замыкания дело только путали :)

                              В потоках мы вводим довольно неприятную систему локов и прочего дерьма, с этим связанного; и это тоже мне не нравится.
                              • 0
                                «Им надо координировать действия или обменяться информацией. Как будете действовать? „
                                Я же говорю — вводим принудительную синхронизацию коллбеков. После этого все проблемы координации нас волнуют мало.

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

                                Собственно, про что и речь: для меня нет разницы в асинхронности или многопоточности — в итоге и то, и другое приводит к одним и тем же проблемам, просто в одном случае создание потока и то, что в нем происходит, контролирую я, а в другом — фреймворк.
    • 0
      >, в реальности там все равно будут проблемы с race conditions, одновременным доступом и так далее.

      откройте для себя asynchronous message passing и shared-nothing architecture.
      • 0
        А чего их открывать, и так все понятно. Это способы решения проблем, которые неизбежно вырастают из многозадачности, и которые, в свою очередь, порождают новые проблемы.

        • 0
          Порождают они намного меньше проблем, чем якобы обязательные race conditions и проблемы с одновременным доступом.
          • 0
            Несомненно.
  • +2
    «Синхронное программирование»? WTF? en.wikipedia.org/wiki/Synchronous_programming
    Автору низачот за перевод. Я вчера как раз подобное уже где-то вычитал: «По оценкам учёных, астероид был приблизительно 7,3 м в диаметре, и если бы он вошёл в нашу атмосферу, его энергия составила бы около 22 килотон.»
    Люди, переводите то, в чем разбираетесь.
    • –2
      не придирайтесь, а скажите, как сделать лучше
      • +5
        Как это не придирайтесь? В правилах хабра не запрещено. Я высказываю свое мнение, и мне наплевать, 99% со мной не согласны или 98%.
        Узкий кругозор приводит к тому, что всякие велосипедисты, называющие одни и те же вещи новыми именами, создают впечатление, что изобрели нечто новое. Как я уже указывал выше, терминология в переводе ваще лажовая. Синхронное программирование? Что это? Вот тут список языков синхронного программирования en.wikipedia.org/wiki/Synchronous_programming_language
        А то, что в приложенном списке полезных ссылок «суперматериал», так то экскурс по давно обсосанным (и реализованным в других языках часто куда лучше, чем в питоне) енумераторам (т.е. банально один из видов ленивости вычислений) и потокам.
        Но нет же, «корутины», «сопрограммы», «генераторы», «синхронное программирование» создают такой эффект, что просто у хабрашкололо отвисла челюсть, и топик типа клевый, типа с пивасиком пойдет. Намешали все до кучи в одну кашицу, не разбираясь в сабже толком. Что может быть лучшим показателем этого, чем использование не тех терминов? Корутины, твистер…
        И да, о чем топик? Сейчас тут уже и в камментах даже налепили вместе и веб и многозадачность ОС. Каши уже столько, что каждому наестся хватит. Топик размытой терминологии о том, что… О том, что параллельные вычисления рулят? О том, что ленивые вычисления рулят? О функциональщине? О безопасном хаскеле? О славном питоне, в котором на самом деле те же лямбды кастрированы как нигде? Не топик, а огрызок неизвестного фрукта, пожевать который, конечно, всегда найдутся желающие…
        • +2
          Отличное замечание. Но с терминологией действительно швах. Asynchronous programming/synchronous programming — выражение регулярно встречающееся в оригинале мало связано с вашей ссылкой. Предложите свой вариант перевода, поправить тест не долго.
          • 0
            Приятно удивлен, что вместо ответного отбрасывания помидоров вы вежливо предложили сделать лучше. Но ссылки на источник я не вижу.
            И как ниже заметил TheShock, проблема может быть и в самом исходнике. Но это, конечно, не оправдывает некачественность топика ) Если там и правда лажа, и нельзя с ней ничего сделать, то топик опубликован зря. Во всяком случае, это мое мнение.
        • 0
          а мне в правилах хабра не запрещено делать замечания)
          в вашем комменте есть доля истины.
          но я не вижу проблем в том, чтобы назвать эту штуку синхронным программированием, все прекрасно поняли о чем речь в контексте топика.
          из топика интересных идей не вынес, но это проблема не переводчика, а автора.
          • 0
            Воды в топике много. Но комменты показали, что тема актуальна и интересна. Читая некоторые из них, я понял что не зря потратил пару часов.
        • 0
        • 0
          The term «coroutine» was originated by Melvin Conway in his seminal 1963 paper.[1]
        • 0
          На корутинах написана «Матрица». ИМХО.
  • 0
    Вот смысл в вебе писать асинхронщину, если там и так несколько потоков, каждый из которых занят своим запросом? Оно уже распараллелено.
    А если нужно отдать страничку сейчас и выполнить что-то тяжеловесное в фоне, то в том же ASP (про руби не знаю, не писал) есть примекрно вот такая штука:

    public ActionResult BeginAsyncAction()
    {
    new System.Threading.Thread(delegate
    {
    //Тут выполняем всякие долгие операции
    }).Start();
    return View();
    }
    • 0
      Наглядный пример из веба: чтобы загрузить пачку объектов из стораджа, нужно сходить в 10 шардов, и потом еще в 10 других. Каждый запрос в шард стоит в среднем N миллисекунд. В синхронном варианте это выльется в N*20, в асинхронном — в то же N.
      • –3
        Неверно. Под нагрузкой это будет те же самые N*20, послкольку другие ядра процессора тоже заняты. Более того, это будет медленнее из-за оверхеда на создание и обслуживание доп. потоков и переключение между ними.
        Конечно, если у вас данные тянутся через сеть, то надо распараллеливать. Только вот не должны 20 объектов тянуться через сеть да ещё и из разных мест. Если это происходит, то проблема не в синхронности, а в архитектуре. В любом случае, во фреймворке есть классы для асинхронных запросов, можете их использовать, например, причём вполне даже прозрачно.
        • +2
          Я не псих, чтобы класть 10 шардов хранилища на одну-единственную-ту-же-машину, где крутится веб-сервер. Особенно под нагрузкой. Естественно, данные тянутся через сеть. Будем называть это проблемой в архитектуре.
      • –1
        Это пример не из веба, а из системы с распределенным хранилищем данных. И с точки зрения _веба_, т.е., контроллера, это должен быть один синхронный запрос: ДайДанные(). А уж как его подсистема данных разнесет внутри себя — ее личное дело.
        • +2
          А что, веб начинается только от контроллера и выше? Ну хорошо, а сам веб-сервер к вебу относится? См пример с нжинксом чуть ниже.
          • –1
            Веб начинается от Presentation Layer. Ваша же задача — Service Layer.

            «Ну хорошо, а сам веб-сервер к вебу относится?»
            В контексте разговора — нет. Собственно, именно потому, что вебсервер — это та хрень, которая позволяет нам обрабатывать каждый запрос синхронно, и при этом сохранить независимость обработки одного запроса от другого. Уровень абстракции.
    • 0
      ну а если у вас долгоживущие коннекты? Комет какой-нибудь. Сервер получает изредка что-то из AMPQ и пишет это в клиенту в сокет. И таких вяловисящих коннектов 10 тысяч.
      • –1
        Только не говорите, что будете делать 10 тысяч простаивающих тредов под каждый коннект. См. nginx, который все коннекты держит в одном потоке, а не форкается каждый раз аки апач.
        • 0
          См. Windows Completion Ports, которые отправляют сообщения в свободный поток из пула.
        • 0
          я??? Эээ. Это вы как раз говорите что собираетесь долгие процессы в системных тредах пускать. И что у вас каждым запросом занят один тред.

          Мы то как раз открываем асинхронные соединения и создаем 10 тыс. зеленых тредов в одном настоящем потоке. Ровно так-же как это делает nginx.
    • 0
      Кстати, во втором MVC рекомендуется использовать Async Actions для этого, чтобы не захламлять пул потоков asp.net.
  • +2
    Всему свое место.
    Если мы делаем интерфейс, то появляется много событий и нужно делать асинхронно. Но запросы к серверу должны быть короткими и ясными как выстрел и тут совсем не зачем делать асинхронность, которая, надо признать, всегда усложняет код и поддержку.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Полностью с вами согласен. Будущее за «Акторами» и сервисной моделью.
  • 0
    а как же параллельное программирование? оно тут не поможет?
    • 0
      Одно без другого будет «не-до...» Высший пилотаж, наверное, асинхронный параллельный код. :-)
  • 0
    А мне нравится, как это сделано в AS3. При желании можно написать фреймворк, который бы вынес асинхронность за кулисы.
  • +1
    Разве Ruby 1.9 не имеет Fibers, о который говорит автор, называя их сопрограммамами?
  • 0
    Я что-то не понимаю, о чём статья. Об очередях сообщений? Ну дык они уже давно используются, например, во многих GUI-фреймворках, и никто не жалуется. Ну а зачем использовать их где попало? Ну и тем более, что в ОС как раз есть на низком уровне эта самая «очередь сообщений». Называется планировщик задач. Только там сообщением потенциально может оказаться одна машинная инструкция. И мир уже давно пользуется фичами, предоставляемыми такой вот очередью сообщений.
    • 0
      Не где попало, а в контроллерах веб-приложений. Это действительно очень модная тема именно сейчас.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.