
libuv 线程池
最为人所知的子线程。Libuv 用来处理文件 IO、DNS 和 CPU 密集型的任务。

以异步读取文件为例:

主线程提交任务给线程池后,就可以继续执行其他操作而不需要等待子线程完成,子线程完成任务后会以异步的方式通知主线程。
Workers
利用 node.js 提供的 worker_threads 模块创建的子线程。
V8 Platform 线程池
Node.js 在初始化时会创建一个 WorkerThreadsTaskRunner,在这里完成 gc 任务等;
WorkerThreadsTaskRunner::WorkerThreadsTaskRunner(int thread_pool_size) {
Mutex platform_workers_mutex;
ConditionVariable platform_workers_ready;
Mutex::ScopedLock lock(platform_workers_mutex);
int pending_platform_workers = thread_pool_size;
delayed_task_scheduler_ = std::make_unique<DelayedTaskScheduler>(
&pending_worker_tasks_);
// threads_ 是线程队列
threads_.push_back(delayed_task_scheduler_->Start());
for (int i = 0; i < thread_pool_size; i++) {
PlatformWorkerData* worker_data = new PlatformWorkerData{
&pending_worker_tasks_, &platform_workers_mutex,
&platform_workers_ready, &pending_platform_workers, i
};
std::unique_ptr<uv_thread_t> t { new uv_thread_t() };
// 创建线程
if (uv_thread_create(t.get(), PlatformWorkerThread,
worker_data) != 0) {
break;
}
threads_.push_back(std::move(t));
}
}
首先会根据 thread_pool_size 创建多个子线程。还创建了一个延迟任务的调度器 DelayedTaskScheduler,这个对象里也创建了一个线程。
std::unique_ptr<uv_thread_t> Start() {
auto start_thread = [](void* data) {
// 处理任务
static_cast<DelayedTaskScheduler*>(data)->Run();
};
std::unique_ptr<uv_thread_t> t { new uv_thread_t() };
uv_sem_init(&ready_, 0);
CHECK_EQ(0, uv_thread_create(t.get(), start_thread, this));
uv_sem_wait(&ready_);
uv_sem_destroy(&ready_);
return t;
}
Node.js 初始化后就创建了一波线程,然后 V8 会使用这些线程,我们通过 Post 和 PostDelayedTask 可以看到。

inspector
Node.js 会创建一个新的线程运行调试相关的代码。
void InspectorIo::ThreadMain() {
uv_loop_t loop;
loop.data = nullptr;
int err = uv_loop_init(&loop);
// 获取调试的 host 和端口
std::string host;
int port;
{
ExclusiveAccess<HostPort>::Scoped host_port(host_port_);
host = host_port->host();
port = host_port->port();
}
// 创建服务器,往事件循环注册 fd
InspectorSocketServer server(std::move(delegate),
&loop,
std::move(host),
port,
inspect_publish_uid_);
server.Start();
// 启动事件循环
uv_run(&loop, UV_RUN_DEFAULT);
CheckedUvLoopClose(&loop);
}
开了子线程避免了主线程阻塞时所带来的限制。如果是单线程,主线程阻塞了,调试也无法进行。
Trace
Trace 功能是 Node 10 版本引入的功能,用来追踪 node 的一些性能。这个功能也用到了子线程。
void NodeTraceWriter::InitializeOnThread(uv_loop_t* loop) {
tracing_loop_ = loop;
flush_signal_.data = this;
int err = uv_async_init(tracing_loop_, &flush_signal_,
[](uv_async_t* signal) {
NodeTraceWriter* trace_writer =
ContainerOf(&NodeTraceWriter::flush_signal_, signal);
trace_writer->FlushPrivate();
});
CHECK_EQ(err, 0);
exit_signal_.data = this;
err = uv_async_init(tracing_loop_, &exit_signal_, ExitSignalCb);
}
watchDog
看门狗是计算机中的一个术语,大概就是定时做一些事情的一个程序,比如启动一个定时器定时检测系统是否运行正常,如果系统运行正常,则在定时器超时前重置定时器,如果系统挂了,则看门狗就会发出告警。
watchDog 是 vm 模块的,vm 模块可以执行传入的 js 代码,watchDog 用来设置监控执行时间的,超时就结束执行。
在主线程里执行一段 js 代码,如果死循环了就卡死了,这个 watchDog 就是开一个线程,监控他,超时就终止他的执行,这样不会导致死循环。