v8是支持多线程的,但是v8 isolate一次只能在一个线程中使用。这里的使用包括,访问v8handle或者持有指向v8内部对象的对象指针。这需要调用者的保证,可以按需选择是否需要加锁。出了可以使用任何其他的同步机制外,还必须使用v8::locker 和 v8::Unlocker来保证v8被切换到单线程执行。
下面详细分析一下,V8内部是如何加锁限制,一个ISolate在同一时刻只在一个线程执行。同时1个ISolate又可以在互斥的时间段,在不同的线程中执行。
1)首先考虑,仅仅在一个线程中执行时,v8.Isolate.Scope 会在指定的作用域内,调用v8.internal.Isolate.Enter和v8.internal.Isolate .Exit。然后其内部依靠,线程级别的全局记录+ 链表EntryStackItem 结构记录调用栈 (Isolte,PerIsolateThreadData)。实现了,当线程正在执行某个Isolate时,把之前的信息压栈保护。作用域失效时,恢复为原始Isolate环境。详细见2.2的内容。
2)如果一个线程正在使用Isolate,其他线程如果也调用该Isolate, 需要遵守V8的规范,v8::locker 和 v8::Unlocker。
其内部本质上是一个类似临界区实现,同一线程可以重复进入,内部实际用的是不支持递归调用的读写锁(AcquireSRWLockExclusive).
如代码所示:用读写锁的独占模式,而且读写锁的独占模式,不支持递归调用。不允许同一个线程多次Acquire,会直接卡主不会报错。
3)所以实际流程如下,当某个线程需要加锁前,isolate根据其内部成员thread_manager中管理了当前正在被哪个线程独占。
如果发现是已经被lock,就会跳过,否则同一个线程中重复调用不支持递归的锁,就会直接卡死。
reinterpret_cast<i::Isolate*>(isolate)
然后调用 thread_manager 进行相应的lock
if (!isolate_->thread_manager()->IsLockedByCurrentThread()) {
isolate_->thread_manager()->Lock();
bool IsLockedByCurrentThread() {
return mutex_owner_.Equals(ThreadId::Current());
void ThreadManager::Lock() {
mutex_.Lock();
mutex_owner_ = ThreadId::Current();
DCHECK(IsLockedByCurrentThread());
void ThreadManager::Unlock() {
mutex_owner_ = ThreadId::Invalid();
mutex_.Unlock();
}
* Multiple threads in V8 are allowed, but only one thread at a time is allowed
* to use any given V8 isolate, see the comments in the Isolate class. The
* definition of 'using a V8 isolate' includes accessing handles or holding onto
* object pointers obtained from V8 handles while in the particular V8 isolate.
* It is up to the user of V8 to ensure, perhaps with locking, that this
* constraint is not violated. In addition to any other synchronization
* mechanism that may be used, the v8::Locker and v8::Unlocker classes must be
* used to signal thread switches to V8.
*
* v8::Locker is a scoped lock object. While it's active, i.e. between its
* construction and destruction, the current thread is allowed to use the locked
* isolate. V8 guarantees that an isolate can be locked by at most one thread at
* any time. In other words, the scope of a v8::Locker is a critical section.
*
* Sample usage:
* \code
* ...
* {
* v8::Locker locker(isolate);
* v8::Isolate::Scope isolate_scope(isolate);
* ...
* // Code using V8 and isolate goes here.
* ...
* } // Destructor called here
* \endcode
*
* If you wish to stop using V8 in a thread A you can do this either by
* destroying the v8::Locker object as above or by constructing a v8::Unlocker
* object:
*
* \code
* {
* isolate->Exit();
* v8::Unlocker unlocker(isolate);
* ...
* // Code not using V8 goes here while V8 can run in another thread.
* ...
* } // Destructor called here.
* isolate->Enter();
* \endcode
*
* The Unlocker object is intended for use in a long-running callback from V8,
* where you want to release the V8 lock for other threads to use.
*
* The v8::Locker is a recursive lock, i.e. you can lock more than once in a
* given thread. This can be useful if you have code that can be called either
* from code that holds the lock or from code that does not. The Unlocker is
* not recursive so you can not have several Unlockers on the stack at once, and
* you can not use an Unlocker in a thread that is not inside a Locker's scope.
*
* An unlocker will unlock several lockers if it has to and reinstate the
* correct depth of locking on its destruction, e.g.:
*
* \code
* // V8 not locked.
* {
* v8::Locker locker(isolate);
* Isolate::Scope isolate_scope(isolate);
* // V8 locked.
* {
* v8::Locker another_locker(isolate);
* // V8 still locked (2 levels).
* {
* isolate->Exit();
* v8::Unlocker unlocker(isolate);
* // V8 not locked.
* }
* isolate->Enter();
* // V8 locked again (2 levels).
* }
* // V8 still locked (1 level).
* }
* // V8 Now no longer locked.
* \endcode
*/