我有一个程序,其中一个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
// ....
}我找到了一个与我的问题类似的布格利波特。记者提供了再现问题的最低限度代码:
#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()可以将引擎调用封装在阻塞队列中,但我不知道如何使用它。
提前谢谢。
UPD:我有一个想法,将QJSValue::call()封装到一个线程安全函数中,通过使用QMetaObject::invokeMethod()强制它在QJSEngine的对象线程中调用。
threadsafeqjsengine.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();
}
// ...
};threadsafeqjsengine.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);
}
}
// ...main.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
// ...
}但是问题并没有消失,它像往常一样崩溃了。我做错了什么?
发布于 2022-11-18 12:27:31
我做了一些测试,行为似乎因Qt版本而异。例如,在QT5.12.12 Win10 MSVC中,我甚至无法让您的原始示例崩溃,但它在5.14.2上却很容易崩溃。
这个例子有点做作.如果您想要使JS代码可重用,那么我可能会将其作为模块导入JSE全局对象,并从该模块调用一个函数。模块是缓存的,所以在第一次使用之后,重新导入它(获取引用)并从模块调用函数几乎是“免费”的。那么根本就没有理由保留对JSValue函数的引用。
无论如何,不要问我为什么要这样做,但是在JSEngine::collectGarbage()之后添加一个function.call()似乎可以修复您的示例。或者将计时器间隔减慢到1ms。我怀疑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,
-Max
发布于 2022-10-28 06:38:47
我已经找到了解决办法,它基于bugreport创建者的解决方案。为了我的目的,我提供了一个改编而成并提供了一个例子。
如果QJSEngine和QJSValue方法是通过带有阻塞队列的QMetaObject::invokeMethod在lambda中调用的,那么它可以正常工作,并且不会发生崩溃。
但是另一方面,如果JS脚本评估需要大量时间,它会阻塞所有其他线程,包括GUI。
https://stackoverflow.com/questions/74120887
复制相似问题