# Nodejs  server 與 CPU 密集的任務 -- worker thread

## 簡介

本篇文章將會說明兩大主題：

1. Nodejs Server 遇到 CPU 密集的任務 可能會遇到的 Blocking 問題
    
2. Nodejs 使用 worker thread 解決上述問題的做法
    

## 概念說明

以下為了比較好說明 worker thread

將逐一簡介以下概念

### Server

把 Server 簡化為一個可以處理 Web Request 的服務

每一個 Request 進來 , Server 就會回應一個 response

舉例來說： 網頁服務器會根據傳入網址會應對應的頁面

### Thread

Thread 可以想像是一個最小單位處理 Request 的服務

一個 Server 裏面可以有一個或是多個 Thread 來處理 Request

Single Thread Server: 使用單一 Thread 來服務 Request 的 Server

Multi Thread Server: 使用多個 Thread 來服務 Request 的 Server

![](https://i.imgur.com/Oo8zVQA.png)

為了更加好懂

可以想像 Server 相當於一個餐廳

而 Thread 就相當於是服務生

假設因為不是很受歡迎

餐廳只請了一個服務生來服務同時舉辦3個 Party 的客人

因為只需要處理接待登記的部份

所以當接待完一個 party 的登記後，馬上服務生又可以去服務下一個進入的客人

可以發現服務生接待時間與3個 party 客人活躍時間做一個關係圖如下：

![](https://i.imgur.com/lcMnukt.png)
![](https://i.imgur.com/urG58Ou.png)


上圖所示:
1. Party 所進行的時間不需要 Waiter 一直 standby 
2. Waiter 只有需要登記新的客人才會需要出現 

Request 也可以分為 CPU active Time 還有 不需要 CPU 運算的時間如下圖

![](https://i.imgur.com/tpKFLIl.png)

### Non Blocking I/O

所謂的 Non Blocking I/O 是指: 單一執行緒處理 Request I/O 可以併發處理，處理I/O 的執行緒並不會被某一個 Request 卡住所有其他 I/O

### Blocking I/O

所謂的 Blocking I/O 是指: 單一執行緒處理 Request I/O 無法併發處理，必須處理完當下處理的 Request I/O 才能繼續往下執行其他 Request

### Nodejs 的 single thread

一個 Nodejs 所啟動的服務會使用單一執行緒來執行所有任務

透過 [libuv](https://github.com/libuv/libuv) 的 Event loop 實踐 Non Blocking I/O

### 當 CPU 消耗很多的 Request 出現

因為 Nodejs 執行是使用單一執行緒來執行所有任務

因此當有一個，需要耗時 CPU 很長時間的 Request 出現時

所有其他 Request 都需要等待這個任務執行完 才能使用 request 資源

### 範例

假設寫一個很長的迴圈 如下

```typescript
export const heavilyJob = (totalCount = 20_000_000_000): number => {
  let count = 0;
  console.log({ totalCount });
  for (let i = 0; i < totalCount; i++) {
    count++;
  }
  return count;
};
```

[heavily-job-sample](https://github.com/nodejs-typescript-classroom/cpu_intensive_sample/tree/long-heavily-job)

當執行完

```shell
time curl http://localhost:3000/blocking-route
```

會發現

```shell
time curl http://localhost:3000/
```

都被上一個 heavily-job 卡住

### 解法之一 worker thread

要避免 cpu 資源被佔住的一個作法是啟用 worker_thread

讓 nodejs 開啟另一個 thread 把 cpu 資源與 main Thread 分開

作法如下：

```javascript
import { parentPort } from 'worker_threads';
import { heavilyJob } from './heavily-job';
import { HEAVY_COUNT } from './constant';

parentPort.postMessage(heavilyJob(HEAVY_COUNT));
```

```typescript
const workerPromise: Promise<number> = new Promise<number>(
      (resolve, reject) => {
        const worker = new Worker(path.join(__dirname, './worker.js'));
        worker.on('message', (data: number) => {
          resolve(data);
        });
        worker.on('error', (error) => {
          reject(error);
        });
      },
    );
 const count = await workerPromise;
```

[single-worker-thrread-sample](https://github.com/nodejs-typescript-classroom/cpu_intensive_sample/tree/single-worker-thread)

### 優化

以目前的範例

可以發現 counter 是可以並行運算的

因此，可以透過把 heavy-job 分化給4個 worker 來做優化 

前提是該CPU 需要多個 Core

否則也是循序去執行

```xml
import { heavilyJob } from './heavily-job';
import { HEAVY_COUNT } from './constant';

parentPort.postMessage(heavilyJob(HEAVY_COUNT / workerData.thread_count));
```

```typescript
const workerPromises: Promise<number>[] = [];
    for (let i = 0; i < THREAD_COUNT; i++) {
      workerPromises.push(this.fourWorkerService.createWorker());
    }
    const thread_results = await Promise.all(workerPromises);
    const total =
      thread_results[0] +
      thread_results[1] +
      thread_results[2] +
      thread_results[3];
    return {
      data: total,
      message: 'this is block service',
    };
```

[four-worker-thread-sample](https://github.com/nodejs-typescript-classroom/cpu_intensive_sample/tree/four-worker-thread)
