Всегда ли асинхронный вызов создает / вызывает новый поток?

Всегда ли асинхронный вызов создает новый поток?

Пример:

Если JavaScript является однопоточным, как он может выполнять асинхронную обратную передачу? Действительно ли он блокируется, пока не получит обратный вызов? Если да, то действительно ли это асинхронный вызов?

Ответов (6)

Решение

Это интересный вопрос.

Асинхронное программирование - это парадигма программирования, которая в основном является однопоточным, то есть «следование одному потоку непрерывного выполнения».

Вы ссылаетесь на javascript, поэтому давайте обсудим этот язык в среде веб-браузера. Веб-браузер запускает один поток выполнения javascript в каждом окне, он обрабатывает события (например, onclick = "someFunction ()") и сетевые подключения (например, вызовы xmlhttprequest).

<script>
function performRequest() {
  xmlhttp.open("GET", "someurl", true);
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4) {
      alert(xmlhttp.responseText);
    }
  }
  xmlhttp.send(sometext);
}
</script>
<span onclick="performRequest()">perform request</span>

(Это нерабочий пример, только для демонстрации концепций).

Чтобы делать все асинхронно, у управляющего потока есть так называемый «основной цикл». Основной цикл выглядит примерно так:

while (true) {
    event = nextEvent(all_event_sources);
    handler = findEventHandler(event);
    handler(event);
}

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

Итак, в приведенном выше примере

  1. Когда пользователь щелкает диапазон, будет сгенерировано событие ButtonClicked, findEventHandler () найдет событие onclick в теге span, а затем этот обработчик будет вызван с событием.
  2. Когда создается запрос xmlhttp, он добавляется в список источников событий all_event_sources.
  3. После возврата из функции performRequest () основной цикл ожидает на шаге nextEvent () ответа. На данный момент ничто не блокирует обработку дальнейших событий.
  4. Данные возвращаются с удаленного сервера, nextEvent () возвращает сетевое событие, обработчиком события оказывается метод onreadystatechange (), этот метод вызывается и запускается диалоговое окно alert ().

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

Во многих приложениях с графическим интерфейсом пользователя асинхронный вызов (например, invokeLater в Java) просто добавляет объект Runnable в свою очередь потоков графического интерфейса. Поток графического интерфейса пользователя уже создан, и он не создает новый поток. Но потоки даже не являются строго обязательными для асинхронной системы. Возьмем, например, libevent, который использует select / poll / kqueue и т. Д. Для выполнения неблокирующих вызовов сокетов, которые затем запускают обратные вызовы для вашего кода, полностью без потоков.

Модель Javascript однопоточная . Асинхронный вызов не является новым потоком, а скорее прерывает существующий поток. Это аналог прерываний в ядре.

Да, имеет смысл иметь асинхронные вызовы с одним потоком. Вот как об этом думать: когда вы вызываете функцию в одном потоке, состояние текущего метода помещается в стек (т. Е. Локальные переменные). Подпрограмма вызывается и в конечном итоге возвращается, после чего исходное состояние извлекается из стека.

То же самое происходит и с асинхронным обратным вызовом! Разница в том, что подпрограмма вызывается системой, а не текущим кодом, вызывающим подпрограмму.

В частности, пара замечаний о JavaScript:

XMLHttpRequest s по умолчанию не блокируются. send() Метод возвращает сразу же после того , как запрос был ретранслируются на основной сетевой стек. Ответ от сервера будет планировать вызов вашего обратного вызова в цикле событий, как обсуждалось в других отличных ответах.

Для этого не требуется новый поток. Базовый API сокета можно выбрать, как java.nio.channelsв Java.

Можно создавать синхронные XMLHttpRequest объекты, передавая false в качестве третьего параметра open(). Это приведет send() к блокировке метода до тех пор, пока от сервера не будет получен ответ, тем самым поставив цикл обработки событий на милость задержки сети и потенциально подвешивая браузер до тех пор, пока не истечет время ожидания сети. Это Плохая Вещь ™.

В Firefox 3.5 с Workerклассом будет представлен надежный многопоточный JavaScript . Фоновый код выполняется в совершенно отдельной среде и взаимодействует с окном браузера, планируя обратные вызовы в цикле событий.

Нет, но будет задействовано более одного потока.

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

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

Итак, в итоге: будет задействовано более одного потока, но это не обязательно создаст новый поток.

Я не знаю о javascript, но, например, в мире Windows Forms асинхронные вызовы могут выполняться без нескольких потоков. Это связано с тем, как работает Windows Message Pump. По сути, приложение Windows Forms устанавливает очередь сообщений, через которую Windows помещает сообщения, уведомляющие ее о событиях. Например, если вы переместите мышь, сообщения будут помещены в эту очередь. Приложение Windows Forms будет в бесконечном цикле, потребляя все отправленные ему сообщения. В зависимости от того, что содержится в каждом сообщении, оно, среди прочего, будет перемещать окна, перерисовывать их или даже вызывать определенные пользователем методы. Вызовы методов идентифицируются делегатами. Когда приложение находит экземпляр делегата в очереди, оно с радостью вызывает метод, на который ссылается делегат.

Итак, если вы находитесь в методе, который что-то делает, и хотите запустить некоторую асинхронную работу без создания нового потока, все, что вам нужно сделать, это поместить экземпляр делегата в очередь, используя метод Control.BeginInvoke. На самом деле это не является многопоточным, но если вы бросите в очередь очень маленькие части работы, это будет выглядеть как многопоточное. Если, с другой стороны, вы дадите ему на выполнение метод, требующий много времени, приложение зависнет до тех пор, пока метод не будет выполнен, что будет выглядеть как застрявшее приложение, даже если оно что-то делает.