【问题标题】:Qt GUI doesn't work with std::thread as I expect正如我所料,Qt GUI 不适用于 std::thread
【发布时间】: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(); }

标签: c++ multithreading qt c++11


【解决方案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
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多