企图使用c++线程解决nodejs单线程问题
企图使用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执行层面。
(´-﹏-`;)毁灭吧,我累了