转载本文请联系编程杂技公众号。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
