正如我所料,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 不是在栈上创建线程吗? 通过调试器运行您的程序。哪条线路导致崩溃?注意:“分段错误”仅表示您的程序试图读取/写入计算机内存的一部分被禁止。当您误用指针时,通常会发生这种情况。 @jksh void notify() _observer-&gt;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(&amp;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的主要内容,如果未能解决你的问题,请参考以下文章

如我所料,带有 continue 的 php if 语句以相反的方式工作

Scala:数组和类型擦除

自动化 GUI 测试 [关闭]

如何在普通主机账户上安装 Propel?

d指针中的“d”代表啥?

为啥虚拟键盘不适用于 Qt 中的 QDialog 文本框?