Это звучит так, как будто JavaScript на самом деле все время использовал несколько разных потоков. Почему люди называют JavaScript однопоточным?
Модель программирования в Node.js - это однопоточное событие l oop с доступом к асинхронным операциям, которые используют собственный код для реализации асинхронного поведения для некоторых операций (дисковый ввод-вывод, сеть, таймеры, некоторые криптографические операции и т. д. c ...).
Также имейте в виду, что эта модель программирования не является продуктом JavaScript самого языка . Это продукт того, как JavaScript развертывается в популярных средах, таких как Node.js, и браузерах как реализация, управляемая событиями.
Тот факт, что внутри существует пул потоков собственного кода, который используется для реализации некоторые асинхронные операции, такие как файловый ввод-вывод или некоторые криптографические операции, не меняют того факта, что модель программирования представляет собой однопоточное событие l oop. Пул потоков - это просто то, как выполняется реализация трудоемкой задачи с использованием асинхронного интерфейса через JavaScript. Это деталь реализации, которая не меняет модель программирования JavaScript по сравнению с однопоточной моделью события l oop.
Точно так же тот факт, что вы теперь можете создавать WorkerThreads, на самом деле не меняет основной модель программирования либо потому, что WorkerThreads запускается на отдельной JavaScript виртуальной машине с отдельным событием l oop и не разделяет обычные переменные. Итак, независимо от того, используете ли вы WorkerThreads или нет, вы по-прежнему в значительной степени разрабатываете свой код для управляемой событиями неблокирующей системы.
WorkerThreads действительно позволяет вам избавиться от некоторых трудоемких задач, чтобы получить их вне основного события l oop, чтобы это главное событие l oop оставалось более отзывчивым, и это очень хороший и полезный вариант для некоторых случаев. Но общая модель не меняется. Например, вся сеть по-прежнему управляется событиями, неблокируема и асинхронна. Таким образом, наличие WorkerThreads не означает, что теперь вы можете программировать сеть в JavaScript, как вы иногда делаете в Java, с отдельным потоком для каждого нового входящего запроса. Эта часть модели JavaScript вообще не меняется. Если у вас есть HTTP-сервер в Node.js, он все еще получает один входящий запрос за раз и не начнет обрабатывать следующий входящий запрос, пока этот предыдущий входящий запрос не вернет управление обратно событию l oop.
Также вы должны знать, что текущая реализация WorkerThreads в Node.js довольно тяжеловесна. Создание WorkerThread запускает новую JavaScript виртуальную машину, инициализирует новый глобальный контекст, устанавливает новую кучу, запускает новый сборщик мусора, выделяет некоторую память и т. Д. c ... Хотя в некоторых случаях полезно, эти WorkerThreads намного тяжелее, чем поток уровня ОС. Я думаю о них, как если бы они были почти как мини-дочерние процессы, но с тем преимуществом, что они могут использовать SharedMemory между WorkerThreads или между основным потоком и WorkerThreads, чего нельзя сделать с реальными дочерними процессами.
Или JavaScript действительно постоянно однопоточный, но благодаря мощности рабочих потоков процесс может иметь несколько потоков из JavaScript, которые по-прежнему ведут себя как один поток?
Первый off, в спецификации языка JavaScript нет ничего, что требовало бы однопоточности. Модель однопоточного программирования - это продукт реализации языка JavaScript в популярных средах программирования, таких как Node.js и браузер. Итак, говоря об однопоточности, вы должны говорить о среде программирования (например, Node.js), а не о самом языке.
В Node.js процесс может иметь несколько потоков JavaScript сейчас (с использованием WorkerThreads). Они выполняются независимо, поэтому вы можете получить истинное распараллеливание выполнения JavaScript в нескольких потоках одновременно. Чтобы избежать многих ошибок синхронизации потоков, WorkerThreads запускаются на отдельной виртуальной машине и не имеют общего доступа к переменным других WorkerThreads или основного потока, за исключением очень тщательно выделенных и контролируемых буферов SharedMemory. WorkerThreads обычно взаимодействует с основным потоком, используя передачу сообщений, которая выполняется через событие l oop (таким образом, уровень синхронизации принудительно устанавливается для всех потоков JavaScript). Сообщения не передаются между потоками упреждающим образом - эти коммуникационные сообщения проходят через событие l oop и должны ждать своей очереди для обработки, как и любая другая асинхронная операция в Node.js.
Вот пример реализации с использованием WorkerThreads. Я писал тестовую программу, задача которой заключалась в том, чтобы несколько миллиардов раз выполнить моделирование какой-либо деятельности и записать статистику по всем результатам, чтобы увидеть, насколько случайными были результаты. Некоторые части моделирования включали в себя некоторые криптографические операции, которые отнимали много времени на ЦП. В моем первом поколении кода я выполнял меньшее количество итераций для тестирования, но было ясно, что выполнение требуемых нескольких миллиардов итераций займет много часов.
Благодаря тестированию и измерениям я смог выяснить, какие части кода используют больше всего ЦП, а затем я создал пул WorkerThread (8 рабочих потоков), которому я мог передавать более трудоемкие задания, и они могли работать над ними параллельно. Это уменьшило общее время запуска моделирования в 7 раз.
Теперь я мог бы также использовать для этого дочерние процессы, но они были бы менее эффективны, потому что мне нужно было передавать большие буферы данных между основной поток и workerThread (workerThread обрабатывал данные в этом буфере), и было намного эффективнее сделать это с помощью SharedArrayBuffer, чем передавать данные между родительским и дочерним процессами (что включало бы копирование данных вместо того, чтобы делиться данными).