LoginSignup
0
0

More than 5 years have passed since last update.

Though you are JS programmer, you should understand concurrency control between tasks like multithreaded language

Last updated at Posted at 2019-06-28

I am Ryo Kanbayashi (ryo_grid)
 
It may be common sense, but I personally wanted to share it as it was a new awareness.

Requirement for understanding of this post

  • should know the rough mechanism of the JS engine (event loop...etc)
  • JS is single threaded, but there are also exceptions within the runtime

At first

I wrote a post below.
 
Trying JS thread library called Concurrent.Thread not using worker thread and additional process / Implemented coordinated type pseudo thread with async/await and Promise

in this article, I wrote about using Multithreading with a library called Concurrent.Thread and wrote about implementation of coordinated type pseudo thread with async / await (essentially using coroutines) and Promise.

then, I have wondered...

  • a async function includes call of async function (like offerd function by node.js and which does I/O) with await syntax must be interrupted by other task on event loop
  • if writer of first async function want the function is executed as a transaction (don't want to be interrupted by other task), what writer should do?

Conclusion(1): if you use coroutine on code base and you want make async function which includes async function call with await transactional, you should write codes which make the function transactional

  • I use a word "event task" as a function on event loop on below

I got answer of above question from experts of JS luckily.

  • locking mechanism should be implemented and use it
    • I asked: is it possible to simply lock when assuming the execution mechanism called event loop of JS
    • => implement lock object which returns a Promise when called lock acquire function of the object and the Promise accepts a waiting event task when lock owner release the lock
    • async-lock should be able to be used
  • or setting all tasks on the program in line and execute these sequentially by myself.
    • execution mechanism can be implemented with Array as task queue and Promise chain

the question that I wrote in the "At first" is over.

Does not colutin (yield, sync/await) used code have same problem???

after getting the above answer, I have thinked a lot more...

The reason is that asynchronous functions (I/O or etc) is async function is the functions take a long time to execute. so if the function occupies JS thread (there is only one), JS codes should become to be bankrupt. then, while the async functions which node.js or browser offers is executed, the thread's execution right should be released and other event tasks should be executed.

I think like above and wrote experimental codes.

node.js

  • warikomi is japanese which means interrupt
  • first, setInterval which sets a function for interrupt is called.
  • functions which want to be as transaction are defined and called
  • defined as transactional function includes async function call which does file I/O (read 50MB)
  • two transactional function are defined. first is simple callback version. sencode is using Promise wrapped async function (file read) version.
  • details are omitted, but If not be interrupted, step1, step2, step3 will be outputed continuously on the console. however, if transactional function is interrupted by the timer task registered with setInterval, output of timer task will be wedged between
nodejs_warikomi_experiment.js
const fs = require('fs');
const util = require('util');

function transaction1(callback){
  console.log("transaction1: step1")
  console.log("transaction1. step2 (file read)")
  fs.readFile("./50MB.bin", (err, data) => {
    if (err) throw err;
    console.log("transaction1: step3");
  });
  callback()
}

function transaction2(){
  const readFile = util.promisify(fs.readFile);
  console.log("transaction2: step1")
  console.log("transaction2. step2 (file read)");

  return readFile("./50MB.bin")
          .then((data) => {
            console.log("transaction2: step3");
          })
          .catch((err) => {
            throw err;
          });
}

// output message every 100ms
setInterval(()=>{console.log("he he he, I try warikomi!")}, 100)

// call transaction1 func and then call transaction2 func
transaction1((err)=>{transaction2()})

result is below.
version of node.js is v10.13.0.

PS F:\work\tmp\js_warikomi_testing> node --version
v10.13.0
PS F:\work\tmp\js_warikomi_testing> node .\nodejs_warikomi_experiment.js
transaction1: step1
transaction1. step2 (file read)
transaction2: step1
transaction2. step2 (file read)
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
transaction2: step3
transaction1: step3
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
he he he, I try warikomi!
.....

both transactional function (wanted to be transactional) are interrupted.

Web JS (Google Chrome)

  • replaced local file reading with XMLHttpRequest which loads 10MB file on server
webjs_warikomi_experiment.js
var ajax_util = {
  post: (url) => {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest();
      xhr.open('POST', url, true);
      xhr.onload = () => {
        if (xhr.readyState === 4 && xhr.status === 200) {
          resolve(xhr.response);
        } else {
          reject(new Error(xhr.statusText));
        } };
        xhr.onerror = () => {
          reject(new Error(xhr.statusText));
        };
        xhr.send("");
      });
    }
  }

function transaction1(callback){
  console.log("transaction1: step1")
  console.log("transaction1. step2 (file read)")
  var req = new XMLHttpRequest();
  req.onreadystatechange = function() {
    if (req.readyState == 4) { // communication finished
      if (req.status == 200) { // communication succeeded
        console.log("transaction1: step3");
        callback()
      }
    }
  }
  req.open('POST', '10MB.bin', true);
  req.setRequestHeader('content-type',
    'application/x-www-form-urlencoded;charset=UTF-8');
  req.send("");
}

function transaction2(){
  console.log("transaction2: step1")
  console.log("transaction2. step2 (file read)");

  ajax_util.post("10MB.bin") // read 10MB files from server
          .then((data) => {
            console.log("transaction2: step3");
          })
          .catch((err) => {
            throw err;
          });
}

function start_experiment(){
  // output message every 100msec
  setInterval(()=>{console.log("he he he, I try warikomi!")}, 100)
  // call transaction1 func and then call transaction2 func
  transaction1((err)=>{transaction2()})
}
javascript
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>expment that whether transactional execution can be implemented with normal coding style</title>
    <script type="text/javascript" src="webjs_warikomi_experiment.js"></script>
  </head>
  <body>
    <h1>Please see web console with developer tools of chrome browser or something</h1>
    <script type="text/javascript">
      // call function on webjs_warikomi_experiment.js
      start_experiment();
    </script>
  </body>
</html>

result is below.
version of Chrome is "73.0.3683.75(Official Build) (32 bit)".

devconsole.png

both transactional function (wanted to be transactional) are interrupted.

this experimental code is placed below.

please see console of developer tools.
I advice that after confirmation of experiment result, you had better close tab because timer registered with setInterval is invoked every 100ms in endless.

Cnclution(2)

though you don't use coroutine on code base, if you want make function which includes async function call, you should write codes which make the function transactional!!!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0