我有一个程序,其中一个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
// ...
}
但问题并没有消失,它像往常一样崩溃,我做错了什么?
2条答案
按热度按时间kokeuurv1#
我已经找到了解决方案,它是基于bugreport创建者的解决方案。我adapted it为我的目的,并提供了一个例子。
如果
QJSEngine
和QJSValue
方法在lambda内部通过QMetaObject::invokeMethod
使用阻塞队列调用,它运行良好,没有崩溃。但另一方面,如果JS脚本评估花费大量时间,则会阻塞包括GUI在内的所有其他线程。
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é)。我也尝试添加一个互斥锁(以及其他一些操作),但这似乎没有帮助(尽管如果你要从多个线程调用引擎,这可能是一个好主意)。我还扩展了示例,以限制循环的数量,并(试图)在之后干净地退出。
HTH,