我有
struct MyWidget : QWidget {
// non-GUI related stuff:
int data;
int doSth();
};
我需要从另一个线程(即不是主线程)访问MyWidget
实例。有没有办法安全地做到这一点?我知道我无法访问GUI相关的功能,因为一些后端(例如MacOSX / Cocoa)不支持它。但是,我只需要在此示例中访问data
或doSth()
。但根据我的理解,根本无法保证对象的生命周期 - 即如果具有该窗口小部件的父窗口关闭,MyWidget
实例将被删除。
还是有办法保证一生吗?我猜QSharedPointer
不起作用,因为QWidget
在内部执行其生命周期处理,具体取决于父窗口小部件。 QPointer
当然也无济于事,因为它只是微弱而且没有锁定机制。
我目前的解决方法基本上是:
int widget_doSth(QPointer<MyWidget> w) {
int ret = -1;
execInMainThread_sync([&]() {
if(w)
ret = w->doSth();
});
return ret;
}
(execInMainThread_sync
使用QMetaMethod::invoke
来调用主线程中的方法。)
但是,由于某些特定原因,该解决方法不再起作用(我稍后会解释原因,但这并不重要)。基本上,我无法在主线程中执行某些操作(出于一些复杂的死锁原因)。
我目前正在考虑的另一个解决方法是添加一个全局互斥锁来保护MyWidget
析构函数,而在析构函数中,我正在清理对MyWidget
的其他弱引用。然后,在其他地方,当我需要确保生命周期时,我只需锁定该互斥锁。
我当前的解决方法不再起作用的原因(这仍然是真实情况的简化版本):
在MyWidget
中,data
实际上是PyObject*
。
在主线程中,会调用一些Python代码。 (在我的应用程序的主线程中根本不可能避免任何Python代码调用。)Python代码最终会执行一些import
,这是由一些Python-import-mutex保护的(Python不支持允许并行import
s。)
在其他一些Python线程中,调用了其他一些import
。 import
现在锁定了Python-import-mutex。虽然它正在做它的事情,它在某些时候做了一些GC清理。 GC清理调用某个包含MyWidget
的对象的遍历函数。因此,它必须访问MyWidget
。但是,execInMainThread_sync
(或等效的工作解决方案)将会死锁,因为主线程当前正在等待Python-import-lock。
注意:Python全局解释器锁并不是真正的问题。当然,它会在任何execInMainThread_sync
电话之前解锁。但是,我无法真正检查任何其他潜在的Python /无论锁定。 ESP。我不被允许解锁Python-import-lock - 这是有原因的。
您可能会想到的一个解决方案是在主线程中完全避免使用任何Python代码。但这有很多缺点,例如:它将是缓慢,复杂和丑陋的(GUI基本上只显示来自Python的数据,因此需要一个巨大的代理/包装它)。我认为我仍然需要在某些时候等待Python数据,所以我只是在其他方面介绍可能的死锁情况。
此外,如果我可以安全地从另一个线程访问MyWidget
,所有问题都会消失。与上述相比,引入全局互斥体是一种更清洁,更短的解决方案。
答案 0 :(得分:2)
您可以使用信号/插槽机制,但如果GUI控件的数量很大,则可能会很繁琐。我建议使用单个信号和插槽来控制gui。通过struct
发送更新GUI所需的所有信息。
void SomeWidget::updateGUISlot(struct Info const& info)
{
firstControl->setText(info.text);
secondControl->setValue(info.value);
}
如果删除了收件人,则无需担心发出信号。此详细信息由Qt
处理。或者,您可以在退出GUI线程事件循环后等待线程退出。您需要使用Qt
register结构{{3}}。
编辑:
从我从扩展问题中读到的内容来看,问题与线程之间的通信有关。尝试使用管道,(POSIX)消息队列,套接字或POSIX信号而不是Qt
信号进行线程间通信。
答案 1 :(得分:0)
就我个人而言,我不喜欢GUI内容(即:小部件)具有非GUI相关内容的设计...我认为你应该将这两者相互分开。 Qt需要始终将GUI对象保留在主线程上,但是其他任何东西(QObject派生的)都可以移动到线程(QObject :: moveToThread)。
答案 2 :(得分:0)
似乎您所解释的内容与小部件,Qt或其他任何内容完全无关。这是Python及其线程和锁定结构固有的问题,如果你是多线程则没有意义。 Python基本上假设任何对象都可以从任何线程访问。使用任何其他工具包时都会遇到同样的问题。可能有一种方法告诉Python不要这样做 - 我对cpython实现的细节知之甚少,但那是你需要查看的地方。
GC清理调用某个包含MyWidget
的对象的遍历函数
那是你的问题。您必须确保不会发生此类跨线程GC清理。我不知道你会怎么做:(
我担心的是,尽管每个人都声称只有C / C ++允许你这么大规模地做到这一点,但是你已经悄悄地用巧妙的方式用自己的方式射击自己了。
答案 3 :(得分:0)
我的解决方案:
struct MyWidget : QWidget {
// some non-GUI related stuff:
int someData;
virtual void doSth();
// We reset that in the destructor. When you hold its mutex-lock,
// the ref is either NULL or a valid pointer to this MyWidget.
struct LockedRef {
boost::mutex mutex;
MyWidget* ptr;
LockedRef(MyWidget& w) : ptr(&w) {}
void reset() {
boost::mutex::scoped_lock lock(mutex);
ptr = NULL;
}
};
boost::shared_ptr<LockedRef> selfRef;
struct WeakRef;
struct ScopedRef {
boost::shared_ptr<LockedRef> _ref;
MyWidget* ptr;
bool lock;
ScopedRef(WeakRef& ref);
~ScopedRef();
operator bool() { return ptr; }
MyWidget* operator->() { return ptr; }
};
struct WeakRef {
typedef boost::weak_ptr<LockedRef> Ref;
Ref ref;
WeakRef() {}
WeakRef(MyWidget& w) { ref = w.selfRef; }
ScopedRef scoped() { return ScopedRef(*this); }
};
MyWidget();
~MyWidget();
};
MyWidget::ScopedRef::ScopedRef(WeakRef& ref) : ptr(NULL), lock(true) {
_ref = ref.ref.lock();
if(_ref) {
lock = (QThread::currentThread() == qApp->thread());
if(lock) _ref->mutex.lock();
ptr = _ref->ptr;
}
}
MyWidget::ScopedRef::~ScopedRef() {
if(_ref && lock)
_ref->mutex.unlock();
}
MyWidget::~QtBaseWidget() {
selfRef->reset();
selfRef.reset();
}
MyWidget::MyWidget() {
selfRef = boost::shared_ptr<LockedRef>(new LockedRef(*this));
}
现在,我需要传递一个MyWidget
指针,我正在使用:
MyWidget::WeakRef widget;
我可以从另一个这样的线程中使用它:
MyWidget::ScopedRef widgetRef(widget);
if(widgetRef)
widgetRef->doSth();
这是安全的。只要存在ScopedRef
,就无法删除MyWidget
。它将在其析构函数中阻塞。或者它已被删除并ScopedRef::ptr == NULL
。