c++ 在多线程程序中使用时,QJSEngine崩溃

jhdbpxl9  于 2022-11-19  发布在  其他
关注(0)|答案(2)|浏览(667)

我有一个程序,其中一个QJSEngine对象与几个不同的线程一起使用。应该没有并发访问--创建一个线程,调用或评估某个东西,然后删除这个线程。
然而,在使用QJSEngine时,程序会随机崩溃。所有崩溃都发生在与分配或释放内存相关的私有QJSEngine函数中。示例:

// QV4::PersistentValueStorage::allocate() (qv4persistent.cpp):

Value *PersistentValueStorage::allocate()
{
    Page *p = static_cast<Page *>(firstPage);
    while (p) {
        if (p->header.freeList != -1)
            break;
        p = p->header.next;
    }
    if (!p)
        p = allocatePage(this);

    Value *v = p->values + p->header.freeList;
    p->header.freeList = v->int_32();   // !!! Get SEGFAULT here

    // ....
}

我发现了一个与我的问题类似的bugreport。报告者提供了重现该问题的最小代码:

#include <QCoreApplication>
#include <QJSEngine>
#include <QThread>
#include <QTimer>

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);
    QJSEngine engine;
    engine.installExtensions(QJSEngine::ConsoleExtension);
    engine.evaluate("var iteration = 0;");

    auto function = engine.evaluate(R"((
        function()
        {
            if (++iteration % 100 == 0)
                console.log(iteration);
        }
    ))");

    QThread thread;
    thread.start();

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, &engine, [&]{function.call();}, Qt::DirectConnection);
    timer.moveToThread(&thread); // Comment it if you want to test QJSEngine in the main thread
    QMetaObject::invokeMethod(&timer, "start", Q_ARG(int, 0));

    return app.exec();
}

在主线程(创建QJSEngine的线程)中执行同样的操作不会使程序崩溃。
你能告诉我如何使QJSEngine线程安全的这种情况吗?报告提供了模板函数safeEngineCall()来 Package 阻塞队列中的引擎调用,但我不知道如何使用它。
先谢谢你。

**UPD:**我有一个想法,将QJSValue::call() Package 到一个线程安全的函数中,通过使用QMetaObject::invokeMethod()
线程安全引擎.h

class ThreadSafeQJSEngine : public QObject
{
    Q_OBJECT

    QJSEngine* m_engine;

public:
    ThreadSafeQJSEngine(QObject *parent = Q_NULLPTR);
    virtual ~ThreadSafeQJSEngine();

    // ...

    QJSValue call(QJSValue value, const QJSValueList& args);

    // ...

private slots:
    inline QJSValue call_imp(QJSValue value, const QJSValueList& args) {
        return value.engine() == m_engine ? value.call(args) : QJSValue();
    }
    // ...
};

线程安全引擎.cpp

ThreadSafeQJSEngine::ThreadSafeQJSEngine(QObject *parent)
    : QObject(parent)
{
    m_engine = new QJSEngine;
}

ThreadSafeQJSEngine::~ThreadSafeQJSEngine()
{
    delete m_engine;
}

QJSValue ThreadSafeQJSEngine::call(QJSValue value, const QJSValueList &args)
{
    if (QThread::currentThread() != this->thread())
    {
        QJSValue result;
        QMetaObject::invokeMethod(this,
                                  "call_imp",
                                  Qt::BlockingQueuedConnection,
                                  Q_RETURN_ARG(QJSValue, result),
                                  Q_ARG(QJSValue, value),
                                  Q_ARG(const QJSValueList&, args)
                                  );

        return result;

    }
    else
    {
        return call_imp(value, args);
    }
}

// ...

主文件.cpp

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);
    ThreadSafeQJSEngine engine;

    // The same as before 
    // ...

    QObject::connect(&timer, &QTimer::timeout, &engine, [&]{engine.call(function);}, Qt::DirectConnection);
    
    // The same as before 
    // ...  
}

但问题并没有消失,它像往常一样崩溃,我做错了什么?

kokeuurv

kokeuurv1#

我已经找到了解决方案,它是基于bugreport创建者的解决方案。我adapted it为我的目的,并提供了一个例子。
如果QJSEngineQJSValue方法在lambda内部通过QMetaObject::invokeMethod使用阻塞队列调用,它运行良好,没有崩溃。
但另一方面,如果JS脚本评估花费大量时间,则会阻塞包括GUI在内的所有其他线程。

ve7v8dk2

ve7v8dk22#

我做了一些测试,Qt版本的行为似乎有所不同。例如,对于Qt 5.12.12 Win10 MSVC,我甚至不能让你原来的例子崩溃,但它在5.14.2上很容易崩溃。
这个例子确实有点做作......如果你想制作可重用的JS代码,那么我可能会将它作为一个模块导入到JSE全局对象中,并从该模块中调用一个函数。(获取引用)并从模块中调用函数,那么就没有理由一开始就引用JSValue函数。
无论如何,不要问我为什么,但是在function.call()之后添加一个JSEngine::collectGarbage()似乎可以修复你的例子。* 或者 * 将计时器间隔减慢到1 ms。我 * 怀疑 * JSValue函数对象存在某种竞争条件,让引擎在function.call()之后“收集自己”是这里的最终修复(而不是GC per sé)。
我也尝试添加一个互斥锁(以及其他一些操作),但这似乎没有帮助(尽管如果你要从多个线程调用引擎,这可能是一个好主意)。我还扩展了示例,以限制循环的数量,并(试图)在之后干净地退出。

#include <QCoreApplication>
#include <QJSEngine>
#include <QMutex>
#include <QThread>
#include <QTimer>
#include <QDebug>

int main(int argc, char** argv)
{
    QCoreApplication app(argc, argv);
    QJSEngine engine;
    engine.installExtensions(QJSEngine::ConsoleExtension);

    engine.evaluate("var iteration = 0;");
    static QJSValue function = engine.evaluate(
        "(function() { if ((++iteration) % 100 == 0) console.log(iteration); })"
    );
    if (function.isError()) {
        qDebug() << function.toString();
        return -1;
    }

    const int interval = 0;  // how often; Try >= 1ms and comment the collectGarbage() call
    const int maxIterations = 20000;  // how many times
    int iteration = 0;  // run counter

    QMutex mutex;
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, &engine, [&]
    {
        // QMutexLocker locker(&mutex);  // optional?
        if (!teration) {
             qDebug() << "Calling function from thread" << QThread::currentThread()
                      << "; App thread is" << qApp->thread();
        }
        if (function.isCallable()) {
          function.call();
          engine.collectGarbage();  // < seems to fix things
        }
        else {
          qDebug() << "function is not callable!";
          qApp->exit(1);
        }
        if (iteration == maxIterations) {
          qApp->exit(0);
        }
    }, Qt::DirectConnection);

    if (true)  // false to test QJSEngine in the main thread.
    {
      QThread *thread = new QThread;
      // ensure graceful shutdown
      QObject::connect(qApp, &QCoreApplication::aboutToQuit, qApp, [&]() {
        thread->quit();
        thread->wait();
        thread->deleteLater();
      });
      QObject::connect(thread, &QThread::finished, &timer, &QTimer::stop);
      // move timer and start thread
      timer.moveToThread(thread);
      thread->start();
    }
    else {
      QObject::connect(qApp, &QCoreApplication::aboutToQuit, &timer, &QTimer::stop);
    }

    QMetaObject::invokeMethod(&timer, "start", Q_ARG(int, interval));
    return app.exec();
}

HTH,

  • 最大值

相关问题