1

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:

  1. 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!!
  1. 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.

(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>
tigercosmos
  • 345
  • 3
  • 17
  • 1
    On a phone rn so can't clearly read the code in the fiddle (please post **all** relevant code in the question itself.) But it sounds like your problem is simply that you still listen for previous messages. If you want your handler to fire only once, then use the `once` option of addEventListener(eventname, callback, {once:true}). But in your case I suspect you might be better handling all cases in a single handler, or even to use a MessageChannel as a way to promisify the communication with your Worker, see https://stackoverflow.com/a/62077379/3702797 – Kaiido Apr 29 '21 at 14:18
  • @Kaiido Thanks for the advice. I add the source code. `{once:true}` indeed help me solve the problem!! In my case, I would like to avoid using an additional MessageChannel. I also tried to use `removeEventListener` after the barrier has reached, but this method seems not to work. – tigercosmos Apr 29 '21 at 14:30
  • Okay. RemoveEventListener also works: https://jsfiddle.net/p158qhLd/1/ – tigercosmos Apr 29 '21 at 14:37

0 Answers0