正如我所料,Qt GUI 不适用于 std::thread
Posted
技术标签:
【中文标题】正如我所料,Qt GUI 不适用于 std::thread【英文标题】:Qt GUI doesn't work with std::thread as I expect 【发布时间】:2016-04-21 19:25:39 【问题描述】:我项目的核心独立于 GUI 框架,这就是我更喜欢 std::thread 的原因。但是当线程正在使用时,Qt 给了我一个错误。
下级停止了,因为它收到了来自操作系统的信号。
信号名称:SIGSEGV 信号含义:分段错误
//MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <thread>
#include <mutex>
#include <QMainWindow>
namespace Ui class MainWindow;
struct Observer
virtual void notify() = 0;
;
class Core
public:
std::thread *run()
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
void setObserver(Observer *observer) _observer = observer;
int ii() const return _ii;
void nextIi() _ii++;
void lock() _mutex.lock();
bool tryLock() return _mutex.try_lock();
void unlock() _mutex.unlock();
private:
void runP()
for (int i = 1; i <= 1000; i++)
if (i % 10 == 0)
lock();
nextIi();
unlock();
notify();
void notify() _observer->notify(); //!!!
Observer *_observer;
int _ii;
std::mutex _mutex;
;
struct MwObserver : public Observer
explicit MwObserver(struct MainWindow *mainWindow) _mainWindow = mainWindow;
virtual void notify();
MainWindow *_mainWindow;
;
class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow() delete _ui;
void upd();
public slots:
void run() _core.run();
private:
Ui::MainWindow *_ui;
MwObserver _observer;
Core _core;
;
inline void MwObserver::notify() _mainWindow->upd();
#endif
-
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
void MainWindow::upd()
_core.lock();
setWindowTitle(QString::number(_core.ii()));
_core.unlock();
【问题讨论】:
QThread 早在 std::thread 出现之前就已经是跨平台的了。 Ummm 不是在栈上创建线程吗? 通过调试器运行您的程序。哪条线路导致崩溃?注意:“分段错误”仅表示您的程序试图读取/写入计算机内存的一部分被禁止。当您误用指针时,通常会发生这种情况。 @jkshvoid notify() _observer->notify();
【参考方案1】:
这里有多个问题,第一个也是最明显的问题已经被 perencia 注意到了。您正在返回一个指向堆栈变量的指针。在 c++ 术语中,这是不可接受的。
其次。崩溃来自不使用std::thread
,而是来自竞争条件。 Qt 事件循环不了解您的互斥锁,因此您的 setWindowTitle
调用正在引入竞争,从而导致崩溃。
您需要使用QMetaObject::invokeMethod 将函数发布到 Qts 事件循环。
示例: 改变
inline void MwObserver::notify() _mainWindow->upd();
到
inline void MwObserver::notify()
if(!QMetaObject::invokeMethod(_mainWindow, "upd", Qt::QueuedConnection))
std::cerr << " Failed to invoke method" << std::endl;
可能适用其他包括
【讨论】:
你能告诉我我到底要做什么吗? (std::thread *thread = new std::thread(&Core::runP, this); 产生相同的效果。) @ufx 这仅修复了堆栈变量的问题。我将使用 invokeMethod 示例更新答案 它没有帮助我。 在这种情况下我有同样的错误。你没有吗?【参考方案2】:这会从不同于 GUI 线程的线程更新 GUI!这是不允许的。 为什么不使用 QThread 和信号/槽机制来更新您的窗口标题。 Qt 框架自动进行线程切换。
class Core : public QObject
Q_OBJECT
public:
explicit Core(QObject * parent = 0) : QObject(parent)
signals:
void notify();
public slots:
void nextIi() _ii++;
void runP()
for (int i = 1; i <= 1000; i++)
if (i % 10 == 0)
nextIi();
notify();
private:
Q_DISABLE_COPY(Core);
int _ii;
;
class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void run() _th.start();
void upd(int ii) setWindowTitle(QString::number(ii));
private:
Ui::MainWindow *_ui;
Core _core;
QThread _th;
;
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
connect(&_core, SIGNAL(notify(int)), this, SLOT(upd(int)));
_core.moveToThread(&_th);
MainWindow::~MainWindow()
delete _ui;
_th.quit();
_th.wait(1000);
【讨论】:
如果可能的话,我不想让核心依赖于 Qt。【参考方案3】:您正在堆栈上创建线程并返回指向该线程的指针。在run()
之后,该指针不再有效。
【讨论】:
std::thread *thread = new std::thread(&Core::runP, this);
给出同样的效果。【参考方案4】:
除了返回指向堆栈变量的指针和从 QT 未知的线程对象更新 GUI 之外。我没有从您的代码中看到,您在其中设置了 _observer
类的 Core
成员。没有setObserver
调用_core
MainWindow
类的成员。
所以MainWindow
类的构造函数调用_core
成员的构造函数,但之后_core._observer
包含垃圾。我认为这是您的 Segmentaion Fault
调用 Core
类的 notify
方法的原因。
【讨论】:
【参考方案5】:所有问题的答案都已经给出,让我总结一下。
程序崩溃与线程无关,问题在于MainWindow
的_core
成员中的_observer
没有设置。必须添加对 setObserver 的调用。
explicit MainWindow( QWidget *parent = nullptr ) :
QMainWindow( parent ),
_observer( this )
_core.setObserver( &_observer );
这将导致下一个问题,即观察者实际上从另一个线程调用udp
消息,从而导致在不同线程上下文中更新 UI。要解决这个问题,最简单的方法是使用 Qt 的Qt::QueuedConnection
。要启用此功能,我们必须将 upt()
设为一个插槽。
public slots:
void run();
void upd();
然后我们可以在
中使用QMetaObject::invokeMethod
调用它
inline void MwObserver::notify()
QMetaObject::invokeMethod( _mainWindow, "upd", Qt::QueuedConnection );
或通过从QObject
派生MwObserver
来使用信号/插槽连接,给它一个信号,然后将该信号连接到upd
插槽并在notify
中提升信号。
struct MwObserver
: public QObject
, public Observer
Q_OBJECT;
signals:
void sigUpd();
public:
explicit MwObserver( MainWindow *mainWindow );
virtual void notify()
MainWindow *_mainWindow;
;
void MwObserver::notify()
sigUpd();
MwObserver::MwObserver( MainWindow *mainWindow )
_mainWindow = mainWindow;
connect( this, SIGNAL(sigUpd()), _mainWindow, SLOT(upd()) )
【讨论】:
【参考方案6】:免责声明:我有一段时间没有使用 Qt,但在 Linux/UNIX 上使用 X/XMotif,GUI 必须在“主线程”中运行,而不是在衍生线程中运行。也许这适用于您的情况。只是一个想法,让您的 GUI 代码在主线程中运行。
【讨论】:
【参考方案7】:最好的方法是用 QObejct 实例包装纯 C++ 代码,并在此对象从纯 C++ 代码收到一些通知时触发信号。
在你的情况下:
class MwObserver : public QObject, public Observer
Q_OBJECT
public:
explicit MwObserver(QObject *parent)
: QObject(parent)
signals:
void SomeEvent();
protected:
// Observer
void notify()
emit SomeEvent();
;
现在 MainWindow 应该将一些插槽连接到以这种方式提供的信号,并且一切都应该开箱即用(Qt 将在幕后进行线程跳转)。
在your code form comment 中,崩溃是由临时对象的无效使用引起的。无论返回何种对象,这都是INVALID C++代码:
std::thread *run()
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
你不能返回一个指向函数方法的本地对象的指针,因为当你返回一个函数时这个对象立即变得无效。这是基本的 C++ 知识。
【讨论】:
查看更新。另外我建议你放弃使用线程。它们很难学习和维护。所犯的错误表明您对 C++ 本身的基本知识有问题。所以在使用线程之前先学习基本的东西。 我可以换成void
。无论如何,我还没有使用这个函数的返回值。我在这里做错了什么? pastebin.com/GBjnK5Va以上是关于正如我所料,Qt GUI 不适用于 std::thread的主要内容,如果未能解决你的问题,请参考以下文章