Javascript 来自C+的节点FFI回调+;线

Javascript 来自C+的节点FFI回调+;线,javascript,c++,multithreading,electron,node-ffi,Javascript,C++,Multithreading,Electron,Node Ffi,我今天遇到了一个令人沮丧的问题。我正在使用节点FFI来运行我的电子应用程序中的C++代码。总的来说,我有很好的经验,但我今天开始使用多线程,遇到了一些困难。我传入的ffi回调是从线程调用的。但是,当我结束循环并尝试将循环线程加入主线程时,它会完全冻结electron应用程序 完全免责声明:我对C++非常陌生,并且会感谢我的代码中的任何反馈来改进它,特别是你认为我应该注意的任何红旗。 下面是两份回购协议,它们证明了我遇到的错误: 电子项目- C++ DLL—< /P> 下面是我所做工作的概述: 在

我今天遇到了一个令人沮丧的问题。我正在使用<代码>节点FFI<代码>来运行我的电子应用程序中的C++代码。总的来说,我有很好的经验,但我今天开始使用多线程,遇到了一些困难。我传入的
ffi
回调是从线程调用的。但是,当我结束循环并尝试将循环线程加入主线程时,它会完全冻结electron应用程序

完全免责声明:我对C++非常陌生,并且会感谢我的代码中的任何反馈来改进它,特别是你认为我应该注意的任何红旗。 下面是两份回购协议,它们证明了我遇到的错误:
电子项目-
C++ DLL—< /P> 下面是我所做工作的概述:
在我的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();
    }
}
在app.js中运行的是主电子进程。我希望看到

start stream
Frame Ready (3800)
stop stream
end session
但它不显示
结束会话
。但是,如果我在C++中删除了行<代码> FrAffeEdyBy](/Cuff>),它就如预期的那样工作。因此,ffi回调引用在某种程度上破坏了多线程环境。我很想听听你的想法。谢谢

问题 您的应用程序已死锁。在您的示例中,有两个线程:

  • 线程1-在运行
    $npm start
    时创建,以及
  • thread-2-在
    Threader::start()
    中创建
  • 在thread-2中,调用
    frameReadyCB()
    ,它将阻塞线程,直到线程完成。显示回调将在线程1上执行

    不幸的是,线程1已经在忙于第二次设置超时,正在调用
    stopStreaming()
    <代码>线程器::停止尝试加入线程2,直到线程2完成为止

    你现在陷入僵局了。线程2正在等待线程1执行回调,线程1正在等待线程2完成执行。他们俩都在互相等候

    通过节点ffi解决方案 当使用
    async()
    通过节点ffi创建线程时,节点ffi似乎会处理在单独线程上运行的回调。因此,您可以从C++库中删除线程,而从节点库调用<代码> DLLIB .SistPosiv.AsiNC(()){“}”/代码>。 C++解决方案 为了解决这个问题,您需要确保在线程2等待
    frameReadyCB()
    完成时,您永远不会尝试加入线程2。您可以使用互斥锁执行此操作。另外,当线程2等待
    frameReadyCB()
    时,您需要确保不要等待锁定互斥锁。唯一的方法是创建另一个线程来停止流。下面的例子是使用节点FFI <代码> Aycy,虽然它可以在C++库内完成,以将其隐藏在节点库中。
    //Threader.h
    #布拉格语一次
    #包括
    #包括
    使用std::线程;
    使用std::原子;
    使用std::mutex;
    类螺纹机
    {
    公众:
    螺纹机(void(*frameReadyCB)();
    ~Threader();
    void start();
    无效停止();
    私人:
    void renderLoop();
    原子线程;
    无效(*frameReadyCB)();
    线程读取;
    互斥mtx;
    };
    
    //Threader.cpp
    #包括“Threader.h”
    线程器::线程器(void(*frameReadyCB)():
    isThreading{false},
    frameReadyCB{frameReadyCB}
    {
    }
    Threader::~Threader()
    {
    停止();
    }
    void Threader::start()
    {
    isThreading=true;
    myThread=thread(&Threader::renderLoop,this);
    }
    void Threader::stop()
    {
    isThreading=false;
    mtx.lock();
    if(myThread.joinable())myThread.join();
    mtx.unlock();
    }
    void Threader::renderLoop()
    {
    while(isThreading){
    mtx.lock();
    frameReadyCB();
    mtx.unlock();
    }
    }
    
    //threadeddl.js
    常数ffi=要求(“ffi”);
    const path=require('path');
    const DllPath=path.resolve(_dirname,'../dll/threadeddl.dll');
    //以FFI期望的方式映射库函数
    常数DllMap={
    'beginSession':['void',['pointer']],
    '结束会话':['无效',[]],
    “startStreaming”:[“void”,[],
    “stopStreaming”:[“void”,[],
    };
    //使用ffi、DLL和函数表创建库
    常量DllLib=ffi.Library(DllPath,DllMap);
    类threadedll{
    构造函数(args){
    this.frameReadyCB=ffi.Callback('void',[],()=>{
    console.log('Frame Ready');
    });
    DllLib.beginSession(this.frameReadyCB);
    }
    startStreaming(){
    DllLib.startStreaming();
    }
    停止流式处理(){
    DllLib.stopStreaming.async(()=>{});
    }
    (完){
    DllLib.endSession.async(()=>{});
    }
    }
    module.exports=threadeddl;
    
    start stream
    Frame Ready (3800)
    stop stream
    end session