企圖使用C++執行緒解決nodejs單執行緒效能問題時遇到的問題
首先我的C++程式碼如下
// AsyncThread.hpp
#include <napi.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <vector>
#include <functional>
#include <queue>
#include <mutex>
#include <condition_variable>
#include "MyThreadPool.hpp"
using namespace std;
CppHelpper::MyThreadPool myThreadPool(5);
using Context = Napi::Reference<Napi::Value>;
void cppCallback(Napi::Env env, Napi::Function jsCallback, Context *context, void *any);
using TSFN = Napi::TypedThreadSafeFunction<Context, void, cppCallback>;
using FinalizerDataType = void;
void cppCallback(Napi::Env env, Napi::Function jsCallback, Context *context, void *info)
{
jsCallback.Call({});
if (info != NULL && info != nullptr)
{
delete info;
}
}
// 處理非同步回撥
Napi::Value AsyncWork(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
// 當前回撥上下文
Context *context = new Napi::Reference<Napi::Value>(Napi::Persistent(info.This()));
Napi::Function jsAsyncCallbackFunction = info[0].As<Napi::Function>();
// 建立一個TypedThreadSafeFunction
TSFN tsfn = TSFN::New(
env,
jsAsyncCallbackFunction, // JavaScript function called asynchronously
"jsAsyncCallbackFunction", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
context,
[](Napi::Env env, FinalizerDataType *finalizerDataType, Context *ctx) {
delete ctx; // 手動釋放
});
myThreadPool.submitTask([tsfn]{
// 執行js呼叫
napi_status status = tsfn.BlockingCall();
// napi_status status = tsfn.NonBlockingCall();
if ( status != napi_ok ){
cout<<"BlockingCall err: "+status<<endl;
}
// 釋放執行緒安全函式
tsfn.Release();
});
return Napi::Boolean::New(env, true);
}
namespace AsyncThreadCall
{
// 真正執行緒的非同步呼叫
Napi::Value callAsyncThread(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsFunction())
{
env.RunScript("async worker is required:callAsyncThread(callback:()=>void)");
return Napi::Boolean::New(info.Env(), false);
}
else
{
AsyncWork(info);
return Napi::Boolean::New(info.Env(), true);
}
}
// 程式退出切記清除執行緒池
Napi::Value cleanThreadPool(const Napi::CallbackInfo &info)
{
myThreadPool.killAll();
return Napi::Boolean::New(info.Env(), true);
}
}
在這裡就不放出執行緒池的程式碼了,但測試執行緒池肯定沒問題的。上面程式碼簡單說一下是在幹什麼
透過將js的回撥函式傳入addon,在C++層面建立執行緒去執行js回撥,從此實現真正的多執行緒nodejs。
但理想很美好,現實很骨感,當我的測試程式碼如下時
const _Scanner2905_Addon_ = require("./Scanner2905.node");
_Scanner2905_Addon_.callAsyncThread(async () => {
while(true){
}
});
_Scanner2905_Addon_.callAsyncThread(async () => {
for(let i=0; i<20; i++){
console.log("(2)"+i);
}
});
_Scanner2905_Addon_.callAsyncThread(async () => {
for(let i=0; i<20; i++){
console.log("(3)"+i);
}
});
_Scanner2905_Addon_.cleanThreadPool();
講道理就算在上面的while(true)死迴圈,理論上不影響底下的迴圈正常執行,因為我在c++層面是透過多執行緒執行的,但實際上如果先執行了while(true),nodejs主執行緒依舊會卡住,底下的迴圈也不執行。why??
原本以為已經飛昇js到達cpp層面的我,不會再受到js的限制,沒想到nodejs的迴圈事件才是高手。即便我透過多執行緒呼叫了BlockCall(NonBlockCall)呼叫回撥,我依舊會受到迴圈事件影響。原因很簡單,我在cpp層面確實是真實的多執行緒,但是最後落到執行層面(也就是js回撥),他依舊會在迴圈事件裡,迴圈事件如果卡住,後續的所有nodejs執行也會卡住。
像不像你在公司,多個領導指揮你幹活一樣??他們以為很多領導發出多個命令,就能讓你也多執行緒執行一樣,但你就是你只有一個。同理,nodejs迴圈事件也就是單執行緒,再多的執行緒去call,他也是在單執行緒迴圈事件裡。
結論,如果真要實現在nodejs裡透過自己實現真正的多執行緒非同步,除非去深度修改迴圈事件,不然不可能實現(我真不想用官方的WorkerThreads,就好比為了實現子執行緒,我重新開一個v8環境執行js,那不是廢話嗎);換個思路,如果在nodejs真需要子執行緒的,可以把邏輯提煉出來交給c++去做,而不是再次把邏輯放到js執行層面。