来自C ++线程

时间:2017-06-24 00:58:55

标签: javascript c++ multithreading electron node-ffi

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

完全免责声明:我对C ++很陌生,并希望对我的代码提出任何反馈意见,以改善它,特别是你认为我应该注意的任何红旗。

以下两个回购展示了我遇到的错误:
电子项目 - 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。但是,如果我删除c ++中的行frameReadyCB(),它会按预期工作。所以ffi回调参考不知何故搞砸了多线程环境。很想对此有所了解。谢谢!

1 个答案:

答案 0 :(得分:0)

问题

您的申请已陷入僵局。在您的示例中,您有两个线程:

  1. thread-1 - 在您运行$ npm start
  2. 时创建
  3. thread-2 - 在Threader::start()
  4. 中创建

    thread-2 中,您调用frameReadyCB(),它将阻止该线程,直到它完成为止。 previous answer显示回调将在 thread-1 上执行。

    不幸的是, thread-1 已经忙于第二个setTimeout,正在调用stopStreaming()Threader::stop尝试加入 thread-2 ,阻塞直至 thread-2 完成。

    你现在陷入僵局。 thread-2 正在等待 thread-1 执行回调, thread-1 正在等待 thread-2 完成执行。他们都在互相等待。

    通过node-ffi

    的解决方案

    当使用async()通过node-ffi创建线程时,似乎node-ffi处理在单独线程上运行的回调。因此,您可以从C ++库中删除线程,而是从节点库中调用DllLib.startStreaming.async(() => {})

    通过C ++解决方案

    为了解决这个问题,您需要确保在等待frameReadyCB()完成时不要尝试加入 thread-2 。您可以使用互斥锁执行此操作。此外,当 thread-2 等待frameReadyCB()时,您需要确保不要等待锁定互斥锁。执行此操作的唯一方法是创建另一个线程来停止流式传输。下面的示例使用node-ffi async来完成此操作,尽管可以在C ++库中完成,以便从节点库中隐藏它。

    // Threader.h
    #pragma once
    
    #include <thread>
    #include <atomic>
    
    using std::thread;
    using std::atomic;
    using std::mutex;
    
    class Threader
    {
    public:
        Threader(void(*frameReadyCB)());
        ~Threader();
    
        void start();
        void stop();
    private:
        void renderLoop();
    
        atomic<bool> isThreading;
        void(*frameReadyCB)();
        thread myThread;
        mutex mtx;
    };
    
    // Threader.cpp
    #include "Threader.h"
    
    Threader::Threader(void(*frameReadyCB)()) :
        isThreading{ false },
        frameReadyCB{ frameReadyCB }
    {
    }
    
    
    Threader::~Threader()
    {
        stop();
    }
    
    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();
        }
    }
    
    // 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.async(() => {});
        }
    
        endSession() {
            DllLib.endSession.async(() => {});
        }
    }
    
    module.exports = ThreadedDll;