企图使用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执行层面。

posted @ 2024-06-25 14:37  麦块程序猿  阅读(1)  评论(0编辑  收藏  举报