Что такое однопоточность Node? Что случилось с многопоточностью Node? Надеюсь, эта статья прояснит ситуацию.
Время чтения составляет около 10 ~ 13 минут
В этой статье тестируется среда использования:
Система: macOS Мохаве 10.14.2
ЦП: 4 ядра 2,3 ГГц
Node: 10.15.1
Начните с потоков Node.
Большинство людей понимают, что Node является однопоточным, поэтому после запуска Node количество потоков должно быть равно 1. Давайте проведем эксперимент, чтобы убедиться в этом.
setInterval(() => {
console.log(new Date().getTime())
}, 3000)
Вы можете видеть, что процесс Node занимает 7 потоков. Почему 7 тем?
Все мы знаем, что ядром Node является движок v8.После запуска Node будет создан экземпляр v8.Этот экземпляр является многопоточным.
- Основной поток: компиляция и выполнение кода.
- Компиляция/оптимизация потока: код можно оптимизировать во время выполнения основного потока.
- Поток профилировщика: записывает время выполнения профилированного кода и предоставляет Crankshaft основу для оптимизации выполнения кода.
- Несколько потоков сборки мусора.
Поэтому часто говорят, что Node является однопоточным, что означает, что выполнение JavaScript является однопоточным, но среда размещения JavaScript, будь то Node или браузер, многопоточная.
В Node есть два компилятора:
full-codegen: Быстро и легко компилируйте js в простой, но медленный машинный код.
Crankshaft: более сложный оптимизирующий компилятор в реальном времени, который компилирует высокопроизводительный исполняемый код.
Некоторые асинхронные операции ввода-вывода занимают дополнительные потоки.
В приведенном выше примере мы читаем файл во время работы таймера:
const fs = require('fs')
setInterval(() => {
console.log(new Date().getTime())
}, 3000)
fs.readFile('./index.html', () => {})
Количество потоков стало 11, это связано с тем, что в Node есть некоторые операции ввода-вывода (DNS, FS) и некоторые вычисления с интенсивным использованием ЦП (Zlib, Crypto), которые включают пул потоков Node, а размер пула потоков по умолчанию равен 4, потому что пул потоков Число становится 11.
Мы можем вручную изменить размер пула потоков по умолчанию:
process.env.UV_THREADPOOL_SIZE = 64
Одна строка кода легко превращает потоки в 71.
Является ли кластер многопоточным?
Одиночный поток Node также вызывает некоторые проблемы, такие как недозагрузка процессора, неперехваченное исключение может привести к завершению работы всей программы и т. д. Поскольку модуль кластера предоставляется в Node, кластер реализует инкапсуляцию child_process и реализует модель с несколькими процессами, создавая дочерние процессы с помощью метода fork. Например, наиболее часто используемый pm2 является лучшим представителем.
Давайте посмотрим на демонстрацию кластера:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主进程 ${process.pid} 正在运行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
// 工作进程可以共享任何 TCP 连接。
// 在本例子中,共享的是 HTTP 服务器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
На этот раз взгляните на Activity Monitor:
Всего имеется 9 процессов, один из которых является основным процессом, количество процессоров x количество ядер процессора = 2 x 4 = 8 дочерних процессов.
Итак, будь то child_process или cluster, это не многопоточная модель, а многопроцессная модель. Хотя разработчики знают о проблеме однопоточной модели, они принципиально не решают проблему и предоставляют многопроцессный способ имитации многопоточности. Как видно из предыдущих экспериментов, хотя Node (V8) сам по себе имеет возможность многопоточности, разработчики не могут эффективно использовать эту возможность, и нижний уровень Node предоставляет дополнительные способы использования многопоточности. Официальный представитель узла сказал:
Вы можете использовать встроенный пул рабочих узлов Node, разработав надстройку C++. В более старых версиях Node создавайте надстройку C++ с помощью NAN, а в более новых версиях используйте N-API. node-webworker-threads предлагает способ только для JavaScript получить доступ к пулу рабочих узлов.
Но для разработчиков JavaScript никогда не существовало стандартного, простого в использовании способа использования возможностей многопоточности Node.
true — многопоточность узла
До выхода Node 10.5.0 официалы давали экспериментальный модульworker_threadsПредоставляет настоящие многопоточные возможности для Node.
Давайте посмотрим на простую демонстрацию:
const {
isMainThread,
parentPort,
workerData,
threadId,
MessageChannel,
MessagePort,
Worker
} = require('worker_threads');
function mainThread() {
for (let i = 0; i < 5; i++) {
const worker = new Worker(__filename, { workerData: i });
worker.on('exit', code => { console.log(`main: worker stopped with exit code ${code}`); });
worker.on('message', msg => {
console.log(`main: receive ${msg}`);
worker.postMessage(msg + 1);
});
}
}
function workerThread() {
console.log(`worker: workerDate ${workerData}`);
parentPort.on('message', msg => {
console.log(`worker: receive ${msg}`);
}),
parentPort.postMessage(workerData);
}
if (isMainThread) {
mainThread();
} else {
workerThread();
}
Приведенный выше код запускает пять дочерних потоков в основном потоке, и основной поток отправляет простое сообщение дочерним потокам.
Поскольку worker_thread все еще находится в экспериментальной стадии, его необходимо увеличить при запуске.--experimental-worker
флаг, смотреть Activity Monitor после запуска:
Не больше, не меньше, просто еще пять подпотоков.
worker_threadмодуль
worker_thread основной код
В модуле worker_thread есть 4 объекта и 2 класса.
- isMainThread: Является ли это основным потоком, исходный код проходит через
threadId === 0
судить. - MessagePort: используется для связи между потоками, унаследованными от EventEmitter.
- MessageChannel: экземпляр канала для создания асинхронной двусторонней связи.
- threadId: идентификатор потока.
- Рабочий: используется для создания дочерних потоков в основном потоке. Первый параметр — это имя файла, которое представляет запись выполнения дочернего потока.
- parentPort: объект типа MessagePort, представляющий родительский процесс в рабочем потоке, null в основном потоке.
- workerData: используется для передачи данных (копии данных) в основном процессе дочернему процессу.
Давайте посмотрим на пример процесса коммуникации:
const assert = require('assert');
const {
Worker,
MessageChannel,
MessagePort,
isMainThread,
parentPort
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const subChannel = new MessageChannel();
worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
subChannel.port2.on('message', (value) => {
console.log('received:', value);
});
} else {
parentPort.once('message', (value) => {
assert(value.hereIsYourPort instanceof MessagePort);
value.hereIsYourPort.postMessage('the worker is sending this');
value.hereIsYourPort.close();
});
}
Более подробное использование можно посмотретьофициальная документация.
Многопроцессорность против многопоточности
Согласно утверждению в университетском учебнике: «Процесс — наименьшая единица распределения ресурсов, а поток — наименьшая единица планирования ЦП». выбирать разумно в соответствии с нашими потребностями.
Давайте сравним многопоточность и многопроцессорность:
Атрибуты | мультипрогресс | Многопоточность | сравнивать |
---|---|---|---|
данные | Обмен данными сложен и требует IPC; данные разделены, а синхронизация проста | Поскольку данные процесса являются общими, обмен данными прост, а синхронизация сложна. | У каждого свои достоинства |
процессор, память | Занимает много памяти, переключение затруднено, а загрузка процессора низкая. | Низкое использование памяти, простое переключение и высокая загрузка ЦП | Многопоточность лучше |
уничтожить, переключиться | Создание и уничтожение, переключение сложно, а скорость низкая | Создание и уничтожение, переключение просто и быстро | Многопоточность лучше |
coding | Простое кодирование и легкая отладка | Кодирование и отладка сложны | многопроцессорность лучше |
надежность | Процессы выполняются независимо и не влияют друг на друга | Нить разделяет та же участь, что и дыхание | многопроцессорность лучше |
распределенный | Может использоваться для многомашинного многоядерного распределенного, легко расширяемого | Может использоваться только для многоядерных распределенных | многопроцессорность лучше |
Приведенные выше сравнения представляют собой только общие ситуации и не являются абсолютными.
work_thread дает Node настоящие возможности многопоточности, что немаловажно.
Перейдите на официальный аккаунт [Сообщество IVWEB], чтобы увидеть больше статей о галантерейных товарах.