Parallel Execution: The Pattern
Learn about patterns in parallel execution and how to fix race conditions with concurrent tasks.
We'll cover the following...
The pattern
Finally, we can extract our nice little pattern for the parallel execution flow. Let’s represent a generic version of the pattern with the following code:
function makeSampleTask (name) {return (cb) => {console.log(`${name} started`)setTimeout(() => {console.log(`${name} completed`)cb()}, Math.random() * 2000)}}const tasks = [makeSampleTask('Task 1'),makeSampleTask('Task 2'),makeSampleTask('Task 3'),makeSampleTask('Task 4')]let completed = 0tasks.forEach(task => {task(() => {if (++completed === tasks.length) {finish()}})})function finish () {// all the tasks completedconsole.log('All tasks executed!')}
With small modifications, we can adapt the pattern to accumulate the results of each task into a collection, to filter or map the elements of an array, or to invoke the finish()
callback as soon as one or a given number of tasks are completed (this last situation in particular is called competitive race).
The unlimited parallel execution pattern
Run a set of asynchronous tasks in parallel by launching them all at once, and then wait for all of them to complete by counting the number of times their callbacks are invoked.
When we have multiple tasks running in parallel, we might ...