I have a parallel web JS program, which concept is as the following pseudo code:
init_and_wait_all_worker_start();
while(each round) {
workers_do_job1();
barrier();
workers_do_job2();
barrier();
}
There are two versions of the program, which use onmessage
and addEvenListener("message")
, separately, to listen to the messages sent from workers.
The code for asking worker doing jobs and waiting for the barrier is looking like:
await new Promise((resolve, reject) => {
Atomics.store(barrier, 0, 0)
for (let i = 0; i < thread_num; i++) {
// worker sends "done2" when it finished the job2
workers[i].onmessage = function (e) {
if (e.data == "done2") {
Atomics.add(barrier, 0, 1);
if (Atomics.load(barrier, 0) == thread_num) {
// reach the barrier
resolve();
}
}
};
// asking doing job2
workers[i].postMessage({
msg: "job2",
});
}
});
The onmessage
works expectably, but the addEventListener
's one does not, which the addEventListener
's one cannot make the barrier working (the onmessage function will be triggered unexpectly).
For example, when there are 4 workers:
- For a round, the
onmessage
version run like this:
get job1 message from worker1
get job1 message from worker2
get job1 message from worker3
get job1 message from worker4
First Barrier!!
get job2 message from worker1
get job2 message from worker2
get job2 message from worker3
get job2 message from worker4
Second Barrier!!
- but the
addEventListener
one get the result like this
// ...
First Barrier!!
get job1 message from worker1
get job2 message from worker2
get job1 message from worker3
get job2 message from worker4
get job2 message from worker2
get job1 message from worker3
get job2 message from worker2
get job2 message from worker4
Second Barrier!!
The second version's problem is the on-message function defined in addEventListener
is trigger unexpectedly. Even weird, the job1 on-message function is triggered when workers are doing job2.
The only difference is changing the worker.onmessage
syntax to worker.addEventListener()
. As far as I known, the two should work in the same way and should get the same result, which is mentioned in the SPEC:
To receive a message inside the worker, the onmessage event handler IDL attribute is used. onmessage = function (event) { ... }; You can again also use the addEventListener() method. In either case, the data is provided in the event object's data attribute.
- The
onmessage
version is available on JSFiddle: https://jsfiddle.net/4jk0cbe2/ - The
addEventListener
version is available on JSFiddle: https://jsfiddle.net/azspy8ev/
(please open console to see the output difference)
Entire source code of version 1:
<script id="worker" type="app/worker">
addEventListener('message', function (e) {
const data = e.data;
if (data.msg == "start") {
postMessage("done");
} else if (data.msg == "func1") {
postMessage("done1");
} else if (data.msg == "func2") {
postMessage("done2");
}
});
</script>
<script>
main({
thread_num: 4,
});
async function init_workers(thread_num) {
const blob = new Blob([document.querySelector('#worker').textContent]);
const url = window.URL.createObjectURL(blob);
return new Promise((resolve, reject) => {
function handleWorker( /* args */) {
return new Promise((resolve, reject) => {
const worker = new Worker(url);
worker.postMessage({ msg: "start" });
worker.addEventListener("message", function (data) {
resolve(worker);
}, false);
worker.onerror = function (err) {
reject(err)
}
})
}
var workers = [];
for (var i = 0; i < thread_num; i++) {
workers.push(handleWorker( /* arg */))
}
Promise.all(workers)
.then(res => {
console.log("all workers started")
resolve(res);
})
.catch(err => {
reject(err)
});
});
}
async function main(argc) {
const thread_num = argc.thread_num;
workers = await init_workers(thread_num);
var barrier_buf = new SharedArrayBuffer(1 * Int8Array.BYTES_PER_ELEMENT);
var barrier = new Int8Array(barrier_buf);
for (let k = 0; k < 5; k++) {
console.log("Round: ", k)
await new Promise((resolve, reject) => {
Atomics.store(barrier, 0, 0)
for (let i = 0; i < thread_num; i++) {
workers[i].onmessage = function (e) {
if (e.data == "done1") {
console.log(1);
Atomics.add(barrier, 0, 1);
if (Atomics.load(barrier, 0) == thread_num) {
console.log("Reach first barrier.")
resolve();
}
}
};
workers[i].postMessage({
msg: "func1",
});
}
});
console.log("=====================================")
await new Promise((resolve, reject) => {
Atomics.store(barrier, 0, 0)
for (let i = 0; i < thread_num; i++) {
workers[i].onmessage = function (e) {
if (e.data == "done2") {
console.log("2")
Atomics.add(barrier, 0, 1);
if (Atomics.load(barrier, 0) == thread_num) {
console.log("Reach second barrier.")
resolve();
}
}
};
workers[i].postMessage({
msg: "func2",
});
}
});
console.log("++++++++++++++++++++++++++++++++++++++++")
}
}
</script>