断点的实现很复杂。这里不是要解释V8中JS断点是如何实现的,而是从宏观的角度来谈谈断点的实现。这个问题源于最近与一位同事关于V8Inspector实现的讨论。JS断点功能相信大家都用过。当我们设置了一个断点,然后代码执行到这个断点,线程就会停止,然后当我们点击Next时,就会停在下一个断点处。那么这个暂停是什么意思呢?下图是Node.js执行到断点时的调用栈。我们知道,V8有一个调试协议,客户端通过这个协议与V8通信,完成调试。当V8收到客户端的信息并进行处理时,会调用runMessageLoopOnPause。runMessageLoopOnPause是V8提供的契约式API。执行JS断点时将调用它。runMessageLoopOnPause中的操作由V8的用户实现。在看实现之前,我们先想想怎么处理。首先,当JS断点执行时,线程显然会进入停止状态,那么这个停止状态到底是什么意思,如何实现是最关键的问题。这个事件循环的实现有点类似,就是当线程没有任务要处理的时候,它应该怎么办,轮询显然太不可思议了,另外一个就是基于订阅/发布机制实现sleep/wake,比如Node.js的sleep/wake机制就是基于事件驱动模块实现的。类似的Inspector也是这样实现的,只是细节不同,因为如果情况不同,当Node.js处于事件循环的阻塞状态时,任何注册到事件驱动模块的事件都可以唤醒Node。js,但是断点不一样,线程在断点的时候,除了信号,一般的任务,比如文件IO,网络IO等,是不能也不应该能够唤醒线程的,所以这里是一个简单的睡眠/唤醒方法,即条件变量。当线程阻塞在条件变量上时,线程只能通过条件变量被唤醒。回到断点场景,也就是线程只有在client继续执行的时候才能被唤醒。分析完我们再来看看Node.js的实现。voidrunMessageLoopOnPause(intcontext_group_id)override{waiting_for_resume_=true;runMessageLoop();}voidrunMessageLoop(){if(running_nested_loop_)return;running_nested_loop_=true;env_->RunAndClearInterrupts();}running_nested_loop_=false;}重点是WaitForFrontendEvent。boolMainThreadInterface::WaitForFrontendEvent(){dispatching_messages_=false;//如果任务队列为空则阻塞if(dispatching_message_queue_.empty()){Mutex::ScopedLockscoped_lock(requests_lock_);while(requests_.empty())incoming_message_cond_.Wait(scoped_lock);}returntrue;}我们假设此时队列为空,那么线程会阻塞在条件变量incoming_message_cond_中。我们来看看第二个问题怎么说。这个时候线程就阻塞了,那么当客户端点击执行下一步的时候,Node.js是怎么处理的呢?这里就需要子线程的帮助了,所以在Node.js中,与客户端的数据通信都是在子线程中完成的,没有过多的代码和细节,看一个调用栈就可以了。这是客户端和Node.js子线程成功建立websocket连接后的调用栈,后面的数据通信类似。看看帖子。voidMainThreadInterface::Post(std::unique_ptr
