我今天遇到了一个令人沮丧的问题。我正在使用node-ffi
在我的电子应用程序中运行 C++ 代码。总的来说,我有很好的经验,但是我今天开始使用多线程并遇到了一些困难。我传入的ffi
回调是从线程调用的就好了。但是,当我结束循环并尝试join
将循环线程连接到主线程时,它会完全冻结电子应用程序。
完全免责声明:我对 C++ 还很陌生,如果对我的代码提供任何反馈以改进它,我将不胜感激,尤其是您认为我应该注意的任何危险信号。
这里有两个 repos 演示了我遇到的错误:
Electron Project - https://github.com/JakeDluhy/threading-test
C++ DLL - https://github.com/JakeDluhy/ThreadedDll
以下是我正在做的事情的概述:
在我的 dll 中,我公开了开始/结束会话和开始/停止流式传输的函数。这些调用类实例的引用来实际实现功能。本质上,它是更强大的 C++ 类的 C 包装器。
// ThreadedDll.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#ifdef THREADEDDLL_EXPORTS
#define THREADEDDLL_API __declspec(dllexport)
#else
#define THREADEDDLL_API __declspec(dllimport)
#endif
THREADEDDLL_API void beginSession(void(*frameReadyCB)());
THREADEDDLL_API void endSession();
THREADEDDLL_API void startStreaming();
THREADEDDLL_API void stopStreaming();
#ifdef __cplusplus
}
#endif
// ThreadedDll.cpp
#include "ThreadedDll.h"
#include "Threader.h"
static Threader *threader = NULL;
void beginSession(void(*frameReadyCB)())
{
threader = new Threader(frameReadyCB);
}
void endSession()
{
delete threader;
threader = NULL;
}
void startStreaming()
{
if (threader) threader->start();
}
void stopStreaming()
{
if (threader) threader->stop();
}
下面是这个Threader
类的样子:
// Threader.h
#pragma once
#include <thread>
#include <atomic>
using std::thread;
using std::atomic;
class Threader
{
public:
Threader(void(*frameReadyCB)());
~Threader();
void start();
void stop();
private:
void renderLoop();
atomic<bool> isThreading;
void(*frameReadyCB)();
thread myThread;
};
// Threader.cpp
#include "Threader.h"
Threader::Threader(void(*frameReadyCB)()) :
isThreading{ false },
frameReadyCB{ frameReadyCB }
{
}
Threader::~Threader()
{
if (myThread.joinable()) myThread.join();
}
void Threader::start()
{
isThreading = true;
myThread = thread(&Threader::renderLoop, this);
}
void Threader::stop()
{
isThreading = false;
if (myThread.joinable()) myThread.join();
}
void Threader::renderLoop()
{
while (isThreading) {
frameReadyCB();
}
}
然后这是我使用它的测试javascript:
// ThreadedDll.js
const ffi = require('ffi');
const path = require('path');
const DllPath = path.resolve(__dirname, '../dll/ThreadedDll.dll');
// Map the library functions in the way that FFI expects
const DllMap = {
'beginSession': [ 'void', [ 'pointer' ] ],
'endSession': [ 'void', [] ],
'startStreaming': [ 'void', [] ],
'stopStreaming': [ 'void', [] ],
};
// Create the Library using ffi, the DLL, and the Function Table
const DllLib = ffi.Library(DllPath, DllMap);
class ThreadedDll {
constructor(args) {
this.frameReadyCB = ffi.Callback('void', [], () => {
console.log('Frame Ready');
});
DllLib.beginSession(this.frameReadyCB);
}
startStreaming() {
DllLib.startStreaming();
}
stopStreaming() {
DllLib.stopStreaming();
}
endSession() {
DllLib.endSession();
}
}
module.exports = ThreadedDll;
// app.js
const ThreadedDll = require('./ThreadedDll');
setTimeout(() => {
const threaded = new ThreadedDll();
console.log('start stream');
threaded.startStreaming();
setTimeout(() => {
console.log('stop stream');
threaded.stopStreaming();
console.log('end session');
threaded.endSession();
}, 1000);
}, 2000);
主电子进程在 app.js 中运行。我希望看到
start stream
Frame Ready (3800)
stop stream
end session
但它显示没有end session
。但是,如果我删除frameReadyCB()
c++ 中的行,它会按预期工作。所以不知何故 ffi 回调引用搞砸了多线程环境。很想对此有一些想法。谢谢!