V8 Многопоточная функция
Я пишу плагин Node, и у меня возникают проблемы при попытке вызвать объект функции V8 из рабочего потока C++.
Мой плагин в основном запускает C++ std::thread и входит в цикл ожидания с помощью WaitForSingleOject(), который запускается другим приложением C++ (плагин X-Plane), записывающим в общую память. Я пытаюсь заставить свой плагин Node проснуться, когда сообщается о событии общего доступа Windows, затем вызвать функцию JavaScript, которую я зарегистрировал из приложения узла, которая, в свою очередь, передаст данные, созданные в X-Plane, обратно в Node и веб-мир.
Мне удалось выяснить, как зарегистрировать функцию JavaScript и вызывать ее из C++, но только в основном потоке V8. Я не могу найти способ вызова функции из std::thread.
Я пробовал различные подходы: объекты Locker (переменная success), постоянные функции (не работали), сохранение основного объекта изолята, вход / выход из изолята, но если / когда код в конечном итоге достигает объекта функции, это недопустимо.
Я получаю разные результаты, начиная от сбоя до замораживания, в зависимости от того, создаю ли я различные объекты для блокировки и разблокировки.
Я абсолютно новичок в V8, так что я не совсем уверен, что все делаю правильно. Код, о котором идет речь, выглядит следующим образом:
Если кто-нибудь может помочь вообще, я буду вечно благодарен!
float* mem = 0;
HANDLE event = NULL;
Isolate* thisIsolate;
void readSharedMemory()
{
//Isolate* isolate = Isolate::GetCurrent();
//HandleScope scope(isolate);
thisIsolate->Enter();
v8::Locker locker(thisIsolate);
v8::Isolate::Scope isolateScope(thisIsolate);
//HandleScope scope(thisIsolate);
//v8::Local<Value> myVal = v8::String::NewFromUtf8(isolate, "Plugin world");
v8::Local<Value> myVal = v8::Number::New(thisIsolate, *mem);
// If it get's this far 'myFunction' is not valid
bool isFun = myFunction->IsFunction();
isFun = callbackFunction->IsFunction();
v8::Context *thisContext = *(thisIsolate->GetCurrentContext());
myFunction->Call(thisContext->Global(), 1, &(Handle<Value>(myVal)));
}
void registerCallback(const FunctionCallbackInfo<Value>& args)
{
Isolate* isolate = Isolate::GetCurrent();
v8::Locker locker(isolate);
HandleScope scope(isolate);
/** Standard parameter checking code removed **/
// Various attempts at saving a function object
v8::Local<v8::Value> func = args[0];
bool isFun = func->IsFunction();
Handle<Object> callbackObject = args[0]->ToObject();
callbackFunction = Handle<Function>::Cast(callbackObject);
isFun = callbackFunction->IsFunction();
// save the function call object - This appears to work
myFunction = v8::Function::Cast(*callbackObject);
isFun = myFunction->IsFunction();
// Test the function - this works *without* the Unlocker object below
v8::Local<Value> myVal = v8::String::NewFromUtf8(isolate, "Plugin world");
myFunction->Call(isolate->GetCurrentContext()->Global(), 1, &(Handle<Value>(myVal)));
}
void threadFunc()
{
thisIsolate->Exit();
// If I include this unlocker, the function call test above fails.
// If I don't include it, the app hangs trying to create the locker in 'readSharedMemory()'
//v8::Unlocker unlocker(thisIsolate);
event = OpenEventW(EVENT_ALL_ACCESS, FALSE, L"Global\\myEventObject");
DWORD err = GetLastError();
//thisIsolate = v8::Isolate::New();
std::cout << "Hello from thread" << std::endl;
bool runThread = true;
while (runThread)
{
DWORD dwWaitResult;
DWORD waitTime = 60000;
dwWaitResult = WaitForSingleObject(event, waitTime);
err = GetLastError();
if (dwWaitResult == WAIT_TIMEOUT)
runThread = false;
// event has been signaled - continue
readSharedMemory();
}
}
void init(Handle<Object> exports)
{
/** NODE INITILISATION STUFF REMOVED **/
// save the isolate - Is this a safe thing to do?
thisIsolate = Isolate::GetCurrent();
//Launch a thread
eventThread = std::thread(threadFunc);
}
1 ответ
Вам, вероятно, нужно немного libuv
магия, чтобы получить основной поток node.js/v8 для выполнения вашего обратного вызова из другого потока. Это будет включать в себя:
Дескриптор uv_async_t, который действует как пробуждение для основного потока v8:
extern uv_async_t async;
uv_async_init
вызов, который связывает uv_async_t с циклом V8 по умолчанию:uv_async_init(uv_default_loop(), &async, async_cb_handler);
И обработчик событий, который будет действовать на событие uvasync_t в главном потоке v8:
void async_cb_handler(uv_async_t *handle) { NotifInfo *notif; mutex::scoped_lock sl(zqueue_mutex); while (!zqueue.empty()) { notif = zqueue.front(); handleNotification(notif); delete notif; zqueue.pop(); } }
Наконец, вам также, вероятно, понадобится очередь с защитой мьютекса, чтобы можно было передавать некоторые данные из потока дополнений C++ в Node/V8:
extern mutex zqueue_mutex; extern std::queue<NotifInfo *> zqueue;
Когда что-то происходит в вашем потоке C++, просто вставьте новый элемент в очередь, защищенную мьютексом, и вызовите
uv_async_send
пробудить цикл событий V8 по умолчанию (так называемый "основной поток") для обработки элемента (который затем может вызывать обратные вызовы Javascript)void ozw_watcher_callback(OpenZWave::Notification const *cb, void *ctx) { NotifInfo *notif = new NotifInfo(); notif->type = cb->GetType(); notif->homeid = cb->GetHomeId(); ... mutex::scoped_lock sl(zqueue_mutex); zqueue.push(notif); uv_async_send(&async); }
( Фрагменты кода взяты из официального дополнения Node.JS для OpenZWave)