Node.jsにおける同期/非同期処理に関して

Node.jsの同期/非同期処理に関してアウトプットために本記事を投稿します。

### マルチスレッド

スレッドとはCPU利用の単位です。
CPUとは、Central Processing Unit(中央演算処理装置)の略で、プログラムの命令を解釈して、コンピューター全体の動作を制御する制御装置や演算を行う演算装置などの機能を持つ装置です。
近年のCPUでは、1つのプロセッサ・パッケージ内に複数のプロセッサ・コアを搭載するマルチコアプロセッサを使用しており、それに応じて複数のスレッドを同時に処理できるようになっています。(マルチスレッド)

マルチスレッドを利用するとサーバーは複数のリクエストを受け取った際に、スレッドを増やして対応します。

![SnapCrab_NoName_2021-10-14_15-44-50_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/a6d3f6df-c94a-24c7-ecaa-c62766b57455.png)

しかし、リクエストが大量に届いた際にスレッドが多くなりすぎて、メモリの消費量が多くなったり、効率が悪くなる[C10K問題][URL-C10K問題](クライアント1万台問題)のような現象が起きる可能性があります。

[URL-C10K問題]:https://ja.wikipedia.org/wiki/C10K%E5%95%8F%E9%A1%8C#:~:text=C10K%E5%95%8F%E9%A1%8C%EF%BC%88%E8%8B%B1%E8%AA%9E%3A%20C10K%20problem,%E5%A4%A7%E3%81%8D%E3%81%8F%E4%B8%8B%E3%81%8C%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%A7%E3%81%82%E3%82%8B%E3%80%82


![SnapCrab_NoName_2021-10-14_15-50-35_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/5a90bd57-ce4f-e442-85e4-354f6a4e012c.png)


Node.jsが採用しているシングルスレッドは、C10K問題を解消するのに有効です。

### シングルスレッド
前章でも記載しましたが、Node.jsはシングルスレッドを採用しています。

シングルスレットとは、マルチスレッドと反対で１つのスレッドのみで処理を実行します。
![SnapCrab_NoName_2021-10-14_16-1-50_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/4374d724-9734-9053-fc78-568361328ea2.png)


##### シングルスレッドのメリット
・スレッドが１つであるため、C10K問題を解決できる
##### シングルスレッドのデメリット
・スレッドが１つであるため、時間のかかる処理がある場合に、次の処理の待ち時間が増えてパフォーマンスが下がる可能性がある

Node.jsでは、非同期 I/Oを用いてシングルスレッドのデメリットを解消します。

### 非同期I/O
I/Oとは入出力処理のことを指します。

非同期 I/Oでは、主にノンブロッキングI/Oを使用して非同期に処理をします。
例えば、時間のかかるAという処理の後にBという処理を実行するとします。
同期I/Oの場合は、Aの処理が終了した後に、Bの処理を実行します。
![SnapCrab_NoName_2021-10-14_16-18-12_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/8005f0e9-ce4a-848c-7120-24f30892abc8.png)



非同期I/Oでは、時間のかかる処理Aが終了する前に処理Bを実行して、時間を短縮することができます。

![SnapCrab_NoName_2021-10-14_16-22-17_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1229062/b2ce336c-f19e-acf4-0434-e2fac905a361.png)

時間軸で処理を分散させて、１つのスレッドでも高速に処理を実行することができます。
### ブロッキングとノンブロッキング

#### ブロッキング
ブロッキングとは、１つの処理が終了するまで、他の処理を実行しないことです。
「fs」というNodeパッケージの 「readFileSyncメソッド」などです。

#### ノンブロッキング
ノンブロッキングとは、ブロッキングを行わず、非同期に処理を実行することです。
「fs」というNodeパッケージの 「readFileメソッド」などです。


### 具体例

「readFileSync」メソッドは、ファイルを読み込むブロッキングメソッドであり、同期I/Oを実現します。
「readFile」メソッドは、ファイルを読み込むノンブロッキングメソッドであり、非同期I/O実現します。

```js
fs.readFileSync('./te.txt'); // ファイルが読み込まれるまでここでブロック
console.log("ブロッキングメソッド")

fs.readFile('./te.txt', (err, data) => {
  console.log("readFileメソッドを使用してファイルの読み込みが完了")
});
console.log("ノンブロッキングメソッド")

// 出力結果
ブロッキングメソッド
ノンブロッキングメソッド
readFileメソッドを使用してファイルの読み込みが完了
```
readFileSyncメソッドはブロッキングメソッドであるため、テキストファイルを読み込む処理が終了するまで、次の処理は実行されません。
readFileメソッドは、ノンロッキングメソッドであるため、非同期に次の処理が実行されています。
Node.jsでは、基本的に非同期I/Oを用いて処理を実行し、必要に応じてブロッキングメソッドやコールバック関数/Promise/async/awaitなどを使用して柔軟に同期/非同期処理を実行することができます。

### awaitはブロッキングをしているのか

```js
function a(){
  fs.readFileSync('./te.txt'); // ファイルが読み込まれるまでここでブロック
  console.log("ブロッキングメソッド")
}

async function b(){
  return new Promise(function(resolve){
    fs.readFile('./te.txt', (err, data) => {
        console.log("readFileメソッドを使用してファイルの読み込みが完了")
        resolve()
    })
    console.log("readFileより先に処理されます")
  });
}
(async ()=>{
  a()
  await b()
  console.log("readFileより後に処理されます")
})();

// 出力結果
ブロッキングメソッド
readFileより先に処理されます
readFileメソッドを使用してファイルの読み込みが完了
readFileより後に処理されます
```

上記のコードを作成してawaitがブロッキングを実行しているかを確認しました。
一見同期処理の様に見受けられますが、awaitはブロッキングをしているわけではありません。
以下、参考文献に記載しているページから参照しています。

> await 式は返されたプロミスが履行されるか拒否されるまで実行を中断することで、プロミスを返す関数をあたかも同期しているかのように動作させます。

上記により、awaitは同期のような処理に見えますが、ブロッキングはしていないことがわかります。

### 参考文献
Node.js公式
https://nodejs.org/ja/about/
awaitに関して
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function