Большинство сред параллелизма, о которых я пишу в этом блоге, состоят из множества уровней абстракции. Рассмотрим, например, Task Parallel Library: это оболочка поверх .NET Thread Pool, которая является оболочкой поверх потоков Windows. Это несоответствие низкоуровневых уровней абстракции вызывает определенные ожидания от более новых библиотек, а именно: они должны предоставлять прямой доступ к общему состоянию, обеспечивать механизмы синхронизации, переменные переменные, атомарные примитивы синхронизации,…
Похоже, что JavaScript (HTML5) с его стандартом Web Workers испытывает недостаток в абстракции для потоков в мире JavaScript. Поскольку не существует базовых низкоуровневых библиотек для многопоточных вычислений JavaScript (в браузере), веб-работники могут заново изобретать не только API, но и стиль параллелизма.
Веб-работники предоставляют модель передачи сообщений, в которой сценарии могут обмениваться данными только через четко определенные неизменяемые сообщения, не обмениваться данными и не использовать механизмы синхронизации для сигнализации или целостности данных. Действительно, веб-работники не сталкиваются с классическими проблемами параллелизма, такими как тупики и условия гонки — просто потому, что эти проблемы параллелизма невозможны.
Честно говоря, я немного завидую разработчикам JavaScript, которые теперь могут использовать многопоточность в своих приложениях на стороне браузера. API Web Workers минималистичен, но сделан правильно. Ниже приведен небольшой пример многопоточного поиска простых чисел с помощью Web Workers — если вы ищете более подробное введение и пошаговое руководство, ознакомьтесь со следующими ресурсами:
- Использование веб-работников — Mozilla Developer Network
- Веб-работники в HTML5 — Гил Финк
- Работа с Inline Web Workers — Гил Финк
- WebWorkers — HTML5 — Шломо Голдберг [HEB]
- Web Workers — W3C Draft
Во-первых, пользовательский интерфейс:
<input type="text" id="range_start"
placeholder="start (e.g. 2)" /><br/>
<input type="text" id="range_end"
placeholder="end (e.g. 100000)" /><br/>
<label for="dop">Degree of parallelism:</label>
<input type="range" id="dop"
min="1" max="8" value="4" step="1" /><br/>
<input type="button" id="calculate" value="Calculate" />
Теперь актуальный бизнес. При нажатии кнопки «Рассчитать» мы порождаем указанное количество рабочих потоков, чтобы выполнить расчет в фоновом режиме. Основной поток передает рабочим диапазон простых чисел, с которыми они будут работать, и получает от рабочих отчеты о ходе работы и итоговый счет:
//Some parts of the code elided for clarity
$(document).ready(function () {
$("#calculate").click(function (e) {
e.preventDefault();
var rangeStart = parseInt($("#range_start").val());
var rangeEnd = parseInt($("#range_end").val());
var parallelism = parseInt($("#dop").val());
createWorkers(parallelism, rangeStart, rangeEnd);
});
});
function createWorkers(parallelism, start, end) {
var range = end - start;
var chunk = range / parallelism;
var count = 0;
var done = 0;
for (var i = 0; i < parallelism; ++i) {
var worker = new Worker("prime_finder.js");
worker.onmessage = function(event) {
if (event.data.type === 'DONE') {
++done;
count += event.data.count;
if (done == parallelism) ...
} else if (event.data.type === 'PROGRESS') {
var progress = event.data.value;
...
}
};
var init = {
start: start + i*chunk,
end: start + (i+1)*chunk,
idx: i
};
worker.postMessage(init);
}
}
Обратите внимание на связь между потоками. Основной поток использует Worker.postMessage для предоставления данных рабочему потоку и получает от него обновления статуса с помощью события onmessage. Рабочий поток запускает скрипт prime_finder.js:
//The isPrime function elided for brevity
self.onmessage = function (event) {
var start = event.data.start;
var end = event.data.end;
var size = end - start;
var count = 0;
for (var i = start; i < end; ++i) {
if (isPrime(i)) ++count;
if (i % 1000 === 0) {
self.postMessage({
type: 'PROGRESS',
value: 100.0*((i-start)/size),
idx: event.data.idx
});
}
}
self.postMessage({
type: 'DONE',
count: count,
idx: event.data.idx
});
};
Здесь мы видим противоположное направление — рабочий поток периодически публикует отчеты о проделанной работе и в конечном итоге сообщает о завершении. Все это запускается при получении исходного сообщения из основного потока.

(На этом скриншоте вы можете видеть, как неравномерное распределение работы оказывается — первый поток заканчивается очень быстро, в то время как четвертый поток отстает довольно медленно …)
Чтобы поэкспериментировать с этим кодом, вы можете загрузить полную демонстрационную версию , включая очень маленький сервер Node.js, который обслуживает его.
Если у вас есть опыт разработки на C # или C ++, вам все это кажется неестественным. Где находится общее состояние? Где механизмы синхронизации? Где указатели на функции? — Действительно, может быть страшно написать многопоточную программу, использующую только асинхронную передачу сообщений — но это намного более чистый старт, чем многие библиотеки, которые мы имеем сегодня.
