当前位置: 首页 > 科技观察

通过N-API使用Libuv线程池

时间:2023-03-20 20:26:22 科技观察

转载本文请联系编程杂技公众号。Node.js不适合处理耗时操作是一个老生常谈的问题,Node.js提供了三种解决方案。1子进程2子线程3libuv线程池前两个在开发上效率更高,因为我们只需要写js。但也有一些缺点。1.执行js的开销。2、虽然可以间接使用Libuv线程池,但是受限于Node.js提供的API。3无法利用c/c++层(内置或工业)提供的解决方案。这时候我们可以试试第三种方案。通过N-API直接使用Libuv线程池。让我们看看如何做到这一点。N-API提供了几个API。napi_create_async_work//创建一个worr,但是还没有执行napi_delete_async_work//释放上面创建的work的内存napi_queue_async_work//提交一个work到libuvnapi_cancel_async_work//取消libuv中的task,如果已经在执行,则不能取消next让我们看看如何通过N-API使用Libuv线程池。先看js层。const{submitWork}=require('./build/Release/test.node');submitWork((sum)=>{console.log(sum)})js提交任务,然后传入回调。然后看N-API的代码。napi_valueInit(napi_envenv,napi_valueexports){napi_valuefunc;napi_create_function(env,NULL,NAPI_AUTO_LENGTH,submitWork,NULL,&func);napi_set_named_property(env,exports,"submitWork",func);returnexports;}NAPI_MODULE(MEDUGYNA_exports先定义)函数,再看在核心逻辑。1定义一个保存上下文的结构体structinfo{intsum;//保存计算结果napi_reffunc;//保存回调napi_async_workworker;//保存工作对象};2将任务提交给Libuvstaticnapi_valuesubmitWork(napi_envenv,napi_callback_infoinfo){napi_valueresource_name;napi_statusstatus;size_targc=1;napi_valueargs[1];structinfodata={0,nullptr,nullptr};structinfo*ptr=&data;status=napi_get_cb_info(env,info,&argc,args,NULL,NULL);if(status!=napi_ok){gotodone;}napi_create_reference(env,args[0],1,&ptr->func);status=napi_create_string_utf8(env,"test",NAPI_AUTO_LENGTH,&resource_name);if(status!=napi_ok){gotodone;}//创建作品,保存ptrcontext将在work函数和done函数中使用//提到工作libuvstatus=napi_queue_async_work(env,ptr->worker);done:napi_valueret;napi_create_int32(env,status==napi_ok?0:-1,&ret);returnret;}执行上面的函数,任务就会提交到Libuv线程池。3libuv子线程执行任务voidwork(napi_envenv,void*data){structinfo*arg=(structinfo*)data;printf("doing...\n");intsum=0;for(inti=0;i<10;i++){sum+=i;}arg->sum=sum;}很简单,计算几个数。并保存结果。4回调jsvoiddone(napi_envenv,napi_statusstatus,void*data){structinfo*arg=(structinfo*)data;if(status==napi_cancelled){printf("cancel...");}elseif(status==napi_ok){printf("done...\n");napi_valuecallback;napi_valueglobal;napi_valueresult;napi_valuesum;//获取结果napi_create_int32(env,arg->sum,&sum);napi_get_reference_value(env,arg->func,&callback);napi_get_global(env,&global);//回调jsnapi_call_function(env,global,callback,1,&sum,&result);//清理napi_delete_reference(env,arg->func);napi_delete_async_work(env,arg->worker);}}执行后,我们看到输出了45。接下来我们分析一下大概的流程。首先让我看一下ThreadPoolWork,ThreadPoolWork是对Libuv工作的封装。classThreadPoolWork{public:explicitinlineThreadPoolWork(Environment*env):env_(env){CHECK_NOT_NULL(env);}inlinevirtual~ThreadPoolWork()=default;inlinevoidScheduleWork();inlineintCancelWork();virtualvoidDoThreadPoolWork()=0;virtualvoidAfterThreadPoolWork0(intstat;环境*env()const{returnenv_;}private:Environment*env_;uv_work_twork_req_;};类的定义很简单,主要是对uv_work_t的封装,我们看看每个函数是什么意思,DoThreadPoolWork和AfterThreadPoolWork是虚函数,由子类实现.后面看子类的时候再分析。我们看一下ScheduleWorkvoidThreadPoolWork::ScheduleWork(){env_->IncreaseWaitingRequestCounter();intstatus=uv_queue_work(env_->event_loop(),&work_req_,//Libuv子线程中执行的任务函数[](uv_work_t*req){ThreadPoolWork*self=ContainerOf(&ThreadPoolWork::work_req_,req);self->DoThreadPoolWork();},//任务处理完成后的回调[](uv_work_t*req,intstatus){ThreadPoolWork*self=ContainerOf(&ThreadPoolWork::work_req_,req);self->env_->DecreaseWaitingRequestCounter();self->AfterThreadPoolWork(status);});CHECK_EQ(status,0);}ScheduleWork是负责向Libuv提交任务的函数。然后查看CancelWork。intThreadPoolWork::CancelWork(){returnuv_cancel(reinterpret_cast(&work_req_));}直接调用Libuv的函数取消任务。看完了父类,再来看子类的定义,它是在N-API中实现的。classWork:publicnode::AsyncResource,publicnode::ThreadPoolWork{private:explicitWork(node_napi_envenv,v8::Localasync_resource,v8::Localasync_resource_name,napi_async_execute_callbackexecute,napi_async_complete_callbackcomplete=nullptr,void*data=nullptr):AsyncResource(env->isolate,async_resource,*v8::String::Utf8Value(env->isolate,async_resource_name)),ThreadPoolWork(env->node_env()),_env(env),_data(数据),_execute(执行),_complete(完成){}~Work()override=default;public:staticWork*New(node_napi_envenv,v8::Localasync_resource,v8::Localasync_resource_name,napi_async_execute_callbackexecute,napi_async_complete_callbackcomplete,void*data){returnnewWork(env,async_resource,async_resource_name,execute,complete,data);}//释放该类对象的内部staticvoidDelete(Work*work){deletework;}//执行任务设置的函数voidDoThreadPoolWork()override{_execute(_env,_data);}voidAfterThreadPoolWork(intstatus)override{//执行用户设置的回调_complete(env,ConvertUVErrorCode(status),_data);}private:node_napi_env_env;//用户设置的数据用于保存执行结果等void*_data;//执行任务的函数napi_async_execute_callback_execute;//任务处理完成后的回调napi_async_complete_callback_complete;};在Work类中,我们看到了虚函数DoThreadPoolWork和AfterThreadPoolWork的实现,没有太多的逻辑。最后我们看一下N-API提供的API的实现。napi_statusnapi_create_async_work(napi_envenv,napi_valueasync_resource,napi_valueasync_resource_name,napi_async_execute_callbackexecute,napi_async_complete_callbackcomplete,void*data,napi_async_work*result){v8::Localcontext=env->context();v8::Local>isolate);}v8::Localresource_name;CHECK_TO_STRING(env,context,resource_name,async_resource_name);uvimpl::Work*work=uvimpl::Work::New(reinterpret_cast(env),resource,resource_name,execute,complete,data);*结果=reinterpret_cast(work);returnnapi_clear_last_error(env);}napi_create_async_work本质上是对Work的简单封装,创建一个Work并返回给用户。2napi_delete_async_worknapi_statusnapi_delete_async_work(napi_envenv,napi_async_workwork){CHECK_ENV(env);CHECK_ARG(env,work);uvimpl::Work::Delete(reinterpret_cast(work));任务执行完毕后,释放Work对应的内存。3napi_queue_async_worknapi_statusnapi_queue_async_work(napi_envenv,napi_async_workwork){CHECK_ENV(env);CHECK_ARG(env,work);napi_statusstatus;uv_loop_t*event_loop=nullptr;status=napi_get_uv_event_loop(env,&event_loop);errorif(status!=napi_set)returnnapi_ok);uvimpl::Work*w=reinterpret_cast(work);w->ScheduleWork();returnnapi_clear_last_error(env);}napi_queue_async_work是对ScheduleWork的封装,作用是提交任务到libuv线程池。4napi_cancel_async_worknapi_statusnapi_cancel_async_work(napi_envenv,napi_async_workwork){CHECK_ENV(env);CHECK_ARG(env,work);uvimpl::Work*w=reinterpret_cast(work);CALL_UV(env,w->CancelWork());returnnapi_clear_last_error(env);}napi_cancel_async_work是对CancelWork的封装,取消Libuv线程池的任务。我们层层看,没有太多逻辑,主要是符合N-API规范。总结:通过N-API提供的API,我们不再受限于Nod提供的一些异步接口。/c++,也可以复用业界的一些方案,解决Node.js中的一些耗时任务。存储库:https://github.com/theanarkh/learn-to-write-nodejs-addons