Многие разработчики смотрят на JavaScript и думают, что, поскольку он, кажется, не имеет возможности многопоточности, он не может работать в многозадачном режиме, но это не совсем так. JavaScript имеет фактическую поддержку потоковой передачи с помощью Web Workers, а Node.js предоставляет возможность fork процесса и модуля под названием cluster, что позволяет разработка многопроцессорных приложений.
Хотя эти решения существуют, они выходят за рамки данной статьи, поскольку параллелизм JavaScript - это гораздо больше, чем просто потоки и процессы, поскольку он фактически может выполнять несколько задач одновременно, используя один процесс с одним потоком.
Вы должны быть знакомы с тем, как разрабатываются приложения с использованием цикла выполнения для поддержания работоспособности программного обеспечения и выполнения некоторого кода при каком-либо взаимодействии с пользователем. В JavaScript есть нечто подобное, но люди называют это циклом событий. Хотя обе концепции во многом схожи, цикл обработки событий является немного более детализированным прямо из коробки, чем большинство реализаций цикла выполнения, имеющихся во многих фреймворках.
В JavaScript, когда происходит событие, цикл событий ставит в очередь некоторый код, который будет запускаться в ответ на это событие. Каждый раз, когда появляется возможность что-то запустить, оно запускает следующую задачу в очереди и выполняется до завершения перед запуском другой задачи.
JavaScript - это асинхронный язык с привязкой к вводу-выводу. Это означает, что все, что требует какого-либо ввода-вывода (ввод пользователя, чтение с диска, сетевой ответ, ответы от других процессов,… - на самом деле все, что вы хотите превратить в ввод-вывод), следует обрабатывать асинхронно. Итак, вы регистрируете то, что хотите сделать после получения данных из источника ввода-вывода, и цикл обработки событий вызовет этот код, когда это событие произойдет. Это позволяет циклу обработки событий запускать другой код, который он может иметь в очереди, ожидая наступления событий. Вот как вы можете добиться параллелизма, используя однопроцессное, однопоточное приложение.
В качестве примера я использовал обычный подход, который многие разработчики Java / Android используют для получения информации с сервера:
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
doSomethingWith(httpResponse);
Если вы внимательно посмотрите на эти строки, вы увидите, что выполняете запрос на получение справа от второй строки и ожидаете, что ответ будет возвращен переменной слева. Если серверу требуется 5 секунд для ответа, выполнение второй строки займет 5 секунд. Таким образом, в этот момент вы блокируете выполнение своей программы.
Очевидно, вы не можете запустить это в своем основном потоке, потому что, если вы это сделаете, пользователь не сможет что-либо сделать с вашим приложением, пока он ожидает ответа сервера, поэтому вы в основном собираетесь создать новый поток. (или какая-то абстракция, которая создаст для вас поток, например AsyncTask) и запустит там этот код. Этот новый поток будет заблокирован во время выполнения запроса.
В JavaScript вы бы сделали что-то вроде этого (используя библиотеку request):
request(url, function(error, response) { if (error) { handleError(error); } else { doSomethingWith(response); } });
Здесь вы передаете функцию обратного вызова в качестве аргумента. Этот обратный вызов будет зарегистрирован для запуска циклом событий, когда произойдет соответствующее событие. В отличие от того, что вы видели в примере Java, где вам нужен отдельный поток, чтобы иметь возможность ждать ответа сервера, единственный поток JavaScript не будет блокироваться, ожидая ответа сервера.
Обратите внимание: поскольку нет реального параллелизма (например, ничего не работает одновременно), нет условий гонки на низком уровне, которые могли бы вызвать конфликты на уровне памяти. Кроме того, нет задержки создания потока, поскольку нет необходимости создавать потоки для получения асинхронного потока.
Имейте в виду, что цикл обработки событий в JavaScript отлично подходит для небольших задач, которые не занимают много времени ЦП, потому что, если задача занимает слишком много времени, вы в основном блокируете цикл событий . от выполнения большего количества задач до завершения более крупной задачи. Если вам действительно нужно выполнять большие вычисления, вам следует рассмотреть возможность использования одного из подходов, которые я указал в первом абзаце, или превратить эти вычисления в операции ввода-вывода.