-1

I'm reading YDKJS and early on we are talking about the difference between Async, Parallel and Concurrent Code.

I have a simple Async example:

let output = 0;
const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);

bar( () => {
    output = 1;
});
foo( () => {
    output = 2
});
setTimeout(() => {
    // This Async code should have 2 different outputs
    output;
}, 2000);

The above code can have the 2 answers based on the Math.random timer and the mutable output:

However, I'd like to add a bit more complexity and convert foo and bar to run in parallel... I don't have much understanding on how I can achieve this:

Question: How can we update the code below, so that bar and foo are run in parallel and therefore, output has more than 2 possible outcomes?

Note: this is purely for learning purposes... I want to see the race conditions occure.

let inputA = 10;
let inputB = 11;

const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);


bar( () => {
    inputA++;
    inputB = inputB * inputA;
    inputA = inputA + 3;
});
foo( () => {
    inputB--;
    inputA = 8 + inputB;
    inputB =  inputA * 2;
});
setTimeout(() => {
    // This Parallel code should have more than 2 outputs;
    console.log(inputA, inputB);
}, 2000);
Armeen Moon
  • 18,061
  • 35
  • 120
  • 233

2 Answers2

0

For your original question, you want to see them run in parallel. You can use Promise.all to run multiple async tasks, it will waits for all async tasks resolved and return an output array.

Promise.all executes async tasks by iterating them(in series), not technically executes them in parallel, but they are running in parallel. It will give you a result when all async tasks resolved or rejected if any one of them failed.

Or you can just run them 1 by 1 without Promise.all. they won't block each other, so still in parallel, but you Promise.all just help you handle callback results in one place.

The output will be either 12 or 20, depends on random timeout you set for bar and foo functions.

For race condition, only the setTimeout functions are asynchronous, but all operations in callbacks are synchronous and non-blocking, so the thread won't jump from operations in one callback to another callback unless all operations in that callback are finished.

But in JS, you can still have data race when using SharedArrayBuffer which needs Atomics object to prevent data race.

let output = 0;
let inputA = 10;
let inputB = 11;

const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);

bar( () => {
    inputA++;
    inputB = inputA;
    output = inputA + 1;
});
foo( () => {
    inputB--;
    inputA = inputB;
    output =  inputB * 2;
});


Promise.all([bar(),foo()])
.then(output => console.log('foo and bar tasks finished with output ',output));

setTimeout(() => {
    console.log('output variable value: ', output)
}, 2000);
Colin
  • 1,195
  • 7
  • 10
  • 1
    Please add explanation. What have you changed? Why have you changed? How does it helps readers and resolve OP's issue? – Rajesh Apr 13 '20 at 05:42
  • 1
    This is purely for learning purposes. I want there to be race conditions. In this example, I will always get an array of [4, 5] – Armeen Moon Apr 13 '20 at 05:48
  • The output was for Promise.all, it was the result for bar and foo. I added a new output to console log out after 2s, and it is the output changed by foo and bar function. – Colin Apr 13 '20 at 06:03
0

The race condition you're looking to invoke isn't possible in ordinary Javascript, luckily. Any time you have a synchronous function, once control flow is passed to that function, that function is absolutely guaranteed to run to the end (or throw) before any other Javascript in the environment runs.

For example, given 1000 tasks which are scheduled by setTimeout to occur in the next 1-2 seconds (randomly), and you also invoke the following function after 1.5 seconds:

const fn = () => {
  console.log('1');
  for (let i = 0; i < 1e8; i++) {
  }
  console.log('2');
};

Once fn starts, it will run all of its code (synchronously) before the next random function runs. So even if the random functions call console.log, it is still guaranteed that, in the above situation, 2 will be logged right after 1.

So, in your original example, there are only 2 possibilities:

  • bar's callback runs first, and completely finishes, then foo's callback runs, and completely finishes. Or:
  • foo's callback runs first, and completely finishes, then bar's callback runs, and completely finishes.

Nothing else is possible, even if the random timeouts land on exactly the same number. Control flow can only be in exactly one place in the code at any given time.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • https://github.com/KBPsystem777/You-Dont-Know-JS/blob/master/async%26performance/ch1.md#parallel-threading If JS events sharing the same data executed in parallel, though, the problems would be much more subtle. Consider these two lists of pseudocode tasks as the threads that could respectively run the code in foo() and bar(), and consider what happens if they are running at exactly the same ti – Armeen Moon Apr 13 '20 at 06:25
  • i Just reread lol — JavaScript never shares data across threads, which means that level of nondeterminism isn't a concern. – Armeen Moon Apr 13 '20 at 06:27
  • Is there no true(complete) parallelism then in JavaScript then? – Armeen Moon Apr 13 '20 at 06:31
  • 2
    No two functions can run at the same time unless one is calling the other. There are web workers though (and other host-provided mechanisms), which can run separate JS due to being two separate environments, but that sort of thing is outside of standard plain JS. – CertainPerformance Apr 13 '20 at 06:41
  • FYI, JS does share some data across threads, agents. If nondeterminism is a concern, there are situations similar to race condition in JS. – Colin Apr 13 '20 at 07:57