Как возможно выполнение двух параллельных синхронных операций?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как возможно выполнение двух параллельных синхронных операций?
Это трёхчастный вопрос, который касается фундаментальных концепций параллелизма в JavaScript и браузере. Ответ зависит от того, что мы понимаем под "синхронными" и "параллельными" операциями.
Правда: В одном потоке JS параллелизма нет
JavaScript работает в одном потоке (single-threaded). Две синхронные операции просто не могут выполняться параллельно в одном потоке:
// Это ВСЕГДА выполнится последовательно, не параллельно
function operation1() {
const start = Date.now();
while (Date.now() - start < 2000) {} // 2 секунды
console.log('Operation 1 done');
}
function operation2() {
const start = Date.now();
while (Date.now() - start < 2000) {} // 2 секунды
console.log('Operation 2 done');
}
operation1(); // Заблокирует UI на 2 секунды
operation2(); // Начнёт выполняться только после operation1
// Итого: 4 секунды, UI заморожен
Решение 1: Web Workers (истинный параллелизм)
Для истинного параллелизма в браузере нужны Web Workers - они работают в отдельных потоках:
// main.js
const worker1 = new Worker('worker.js');
const worker2 = new Worker('worker.js');
// Запускаем две операции параллельно в разных потоках
worker1.postMessage({ operation: 'task1', duration: 2000 });
worker2.postMessage({ operation: 'task2', duration: 2000 });
worker1.onmessage = (e) => {
console.log('Worker 1 done:', e.data);
};
worker2.onmessage = (e) => {
console.log('Worker 2 done:', e.data);
};
// Обе операции выполняются параллельно!
// Время: ~2 секунды (параллельно), не 4 (последовательно)
// worker.js - отдельный поток
self.onmessage = (e) => {
const { operation, duration } = e.data;
// Синхронная операция в отдельном потоке
const start = Date.now();
while (Date.now() - start < duration) {}
self.postMessage({ result: `${operation} completed` });
};
Решение 2: Асинхронность (квазипараллелизм)
Если операции асинхронные, они могут выглядеть как параллельные благодаря Event Loop:
// Асинхронные операции
function asyncOperation1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('Operation 1 done');
resolve();
}, 2000);
});
}
function asyncOperation2() {
return new Promise(resolve => {
setTimeout(() => {
console.log('Operation 2 done');
resolve();
}, 2000);
});
}
// Запускаем параллельно
Promise.all([asyncOperation1(), asyncOperation2()]).then(() => {
console.log('Both done');
});
// Вывод:
// Operation 1 done (2сек)
// Operation 2 done (2сек)
// Both done (всего ~2сек, не 4!)
Это работает благодаря Event Loop:
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
setTimeout(() => console.log('Timeout 2'), 0);
fetch('/api/data1').then(() => console.log('Fetch 1 done'));
fetch('/api/data2').then(() => console.log('Fetch 2 done'));
console.log('End');
// Вывод:
// Start
// End
// Timeout 1 (микротаск очередь)
// Timeout 2
// Fetch 1 done (если оба завершились одновременно)
// Fetch 2 done
Решение 3: requestAnimationFrame (для графики)
Для операций, связанных с отрисовкой, можно использовать requestAnimationFrame для неблокирования:
let data1Processed = false;
let data2Processed = false;
function processLargeData1() {
// Обрабатываем кусочек данных
for (let i = 0; i < 10000; i++) {
// Обработка
}
if (hasMoreData) {
requestAnimationFrame(processLargeData1);
} else {
data1Processed = true;
console.log('Data 1 processed');
}
}
function processLargeData2() {
// То же для данных 2
for (let i = 0; i < 10000; i++) {
// Обработка
}
if (hasMoreData) {
requestAnimationFrame(processLargeData2);
} else {
data2Processed = true;
console.log('Data 2 processed');
}
}
// Запускаем обе операции
requestAnimationFrame(processLargeData1);
requestAnimationFrame(processLargeData2);
// UI остаётся отзывчивым, так как операции разбиты на кусочки
Решение 4: setTimeout для разбиения работы
Можно разбить синхронную работу на кусочки:
function heavyOperation1() {
for (let i = 0; i < 1000000000; i++) {
// Тяжёлые вычисления
}
}
function heavyOperation2() {
for (let i = 0; i < 1000000000; i++) {
// Тяжёлые вычисления
}
}
// Плохо: блокирует UI на 4 секунды
// heavyOperation1();
// heavyOperation2();
// Хорошо: разбиваем на части
function chunkWork(operation, chunkSize, onComplete) {
let completed = 0;
function doChunk() {
for (let i = 0; i < chunkSize; i++) {
operation();
completed++;
}
if (completed < 1000000000 / chunkSize) {
setTimeout(doChunk, 0);
} else {
onComplete();
}
}
doChunk();
}
// Запускаем обе операции
chunkWork(heavyOperation1, 100000, () => console.log('Op1 done'));
chunkWork(heavyOperation2, 100000, () => console.log('Op2 done'));
// UI остаётся отзывчивым!
Решение 5: SharedArrayBuffer (для обмена данными между Workers)
Для коммуникации между потоками можно использовать SharedArrayBuffer:
// main.js
const sharedBuffer = new SharedArrayBuffer(4);
const sharedArray = new Int32Array(sharedBuffer);
const worker1 = new Worker('worker1.js');
const worker2 = new Worker('worker2.js');
worker1.postMessage({ buffer: sharedBuffer });
worker2.postMessage({ buffer: sharedBuffer });
// Оба worker'а могут параллельно писать и читать из одного буфера
Внимание: SharedArrayBuffer требует HTTPS и специальные заголовки COOP/COEP.
Таблица сравнения подходов
| Подход | Истинный параллелизм | Блокирует UI | Сложность |
|---|---|---|---|
| Синхронные операции | Нет | Да | Низкая |
| Web Workers | Да | Нет | Средняя |
| Promise/async-await | Нет (квази) | Нет | Средняя |
| requestAnimationFrame | Нет | Нет | Средняя |
| setTimeout chunks | Нет | Нет | Средняя |
| SharedArrayBuffer | Да | Нет | Высокая |
Практический пример: Загрузка файлов параллельно
// Проблема: две загрузки файлов последовательно (неправильно)
async function uploadSequential() {
const file1 = await upload('file1.txt');
const file2 = await upload('file2.txt');
console.log('Both done');
// Время: время1 + время2
}
// Решение: загружаем параллельно (правильно)
async function uploadParallel() {
const [file1, file2] = await Promise.all([
upload('file1.txt'),
upload('file2.txt')
]);
console.log('Both done');
// Время: max(время1, время2)
}
Краткое резюме
- Две синхронные операции в одном потоке - НЕВОЗМОЖНЫ параллельно
- Асинхронные операции - выглядят параллельными благодаря Event Loop
- Истинный параллелизм - нужны Web Workers (отдельные потоки)
- Не блокировать UI - используй async/await, setTimeout chunks или requestAnimationFrame
- Выбирай инструмент - зависит от типа операции (CPU-bound vs I/O-bound)
Вывод: В браузере два синхронных кода не могут выполняться параллельно в одном потоке. Но асинхронные операции могут выглядеть как параллельные, а Web Workers обеспечивают истинный параллелизм в отдельных потоках. Выбор зависит от вашей задачи.