8

我遇到的问题是我决定QThreads按照他们应该的方式实现,基于大量文章:
https ://www.qt.io/blog/2010/06/17/youre-doing-it-wrong
http:// /mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

手头的问题是,由于算法是单独运行的QObject(包装在 中QThread)。我怎样才能说出类似Thread::Sleep或 .. 之类的东西。有什么想法吗?

软件的小描述。基本上我的应用程序解决了TSP(旅行推销员问题)。随着搜索的进行,它将历史记录中的所有状态保存为frames..(如视觉框架)。搜索算法将在一个线程上运行。主线程正在处理 GUI。然后是Mediaplayer类似的线程,它告诉Main线程要在屏幕上显示什么帧。那么睡眠是从哪里来的呢?在 gui 中有一个滑块,用户可以使用它来快进或以正常速度前进。该滑块通过信号槽告诉Mediaplayer线程更快或更慢。

4

4 回答 4

7

我们所做的基本上是这样的:(由内存编写,因为我没有在这台计算机上检查我们的代码)

class Sleeper : public QThread {
public:
   void sleep(int ms) { QThread::sleep(ms); }
};

void sleep(int ms);

// in a .cpp file:
static Sleeper slp;

void sleep(int ms) {
    slp.sleep(ms);
}

关键是QThread::sleep函数导致调用线程休眠,而不是实例所代表的threaf QThread。所以只需创建一个通过自定义子类调用它的包装器QThread

不幸的是,QThread 是一团糟。该文档告诉您错误地使用它。正如您所发现的,一些博客文章告诉您一种更好的方法,但是您不能调用类似 的函数sleep它本来就不应该是受保护的线程成员

最重要的是,无论您使用哪种方式使用 QThread,它都旨在模拟可能是有史以来最糟糕的线程 API,即 Java 线程 API。与理智的东西相比,比如boost::thread,甚至更好,std::thread它臃肿、过于复杂并且不必要地难以使用,并且需要大量的样板代码。

这确实是 Qt 团队搞砸的地方之一。重要时刻。

于 2012-05-27T19:11:39.717 回答
4

简单的答案:你不应该阻塞异步的、运行到完成的代码——a 中的每个事件处理程序和插槽实现QObject都应该尽快完成它的工作并返回。它不应该做任何忙碌的等待或睡眠。有关这条线的更多咆哮,请参阅 Miro Samek 的我讨厌 RTOSes

对于从上面遵循的更好的实现,请参阅此答案下面的宏诡计最好留给被 C 卡住的可怜的灵魂。

我附上了一个示例,说明如何以正确的方式进行操作,至少从代码功能的角度来看。如果你想要一个真正的实现,看看Boost 的 stackless coroutines

宏诡计是句法糖——它使技术更可口(Boost 比我在下面做的更好)。无论您是使用宏还是显式写出方法,都取决于您。语法并不是所谓的“正确方法”。我不是唯一一个使用这种预处理器诡计的人。缺少对嵌套函数调用的支持,以及在QObject. 该示例仅显示了一个“线程”的代码,并且仅显示了一级异步函数调用。Stackless Python得出了合乎逻辑的结论。

如果您以异步方式编写它,您将在所有代码中看到这种模式。宏是语法糖,SLEEP有助于使代码更易于理解。如果没有 C++ 中的 hacky 宏,就没有真正干净的方法来编写它,其中语法不会过于霸道。即使从 C++11 开始,该语言也没有对 yield 的内置支持。请参阅为什么没有将 yield 添加到 C++0x?.

这是真正的非阻塞代码,您会看到周期性计时器事件在您“睡着”时触发。请注意,这种协作式多任务处理的开销比操作系统完成的线程/进程切换要低得多。以这种方式编写 16 位 Windows 应用程序代码是有原因的:它的性能相当好,即使在微不足道的硬件上也是如此。

请注意,此代码不需要 a ,实际上也不需要 a 尽管如果您将对象移动到高优先级线程,则延迟的传播会更小。QThreadQThread

Qt 计时器实现足够聪明,可以减少 Windows 上的计时器滴答周期,如果周期“短”的话。您可以使用我在下面显示的特定于平台的代码,但不建议这样做。在 Qt 5 上,您只需启动一个Qt::PreciseTimer计时器。请注意,在 Windows 8 之前的系统上,您需要权衡功耗和稍高的内核开销来换取性能。Windows 8、OS X (xnu) 和现代 Linux 是无滴答的,不会遭受这种性能下降的影响。

我应该承认使用 ## 和 __LINE__ 创建 C 宏(与定位宏的令牌连接)中明确的预处理器滥用方向。

与宏类似SLEEP(),您也可以实现GOTO()宏,以允许您拥有以更易于遵循的阻塞代码样式编写的简单有限状态机,但在幕后是异步的。您可以使用宏来实现在状态进入和退出等时执行的操作,但代码看起来完全像一个直接编码的阻塞式函数ENTER()LEAVE()我发现它比没有任何语法糖衣的代码效率高,而且更容易理解。YMMV。最后,您将有一些东西正在通往 UML 状态图的路上,但开销(运行时和代码文本方面)比QStateMachine-based实现要少。

下面是输出,星号是周期性的计时器滴答声。

doing something
*
*
*
*
*
*
*
*
*
*
slept, a=10
*
*
*
*
*
slept, a=20
*
*
slept, a=30
*
slept, a=40
#sleep.pro
QT       += core
QT       -= gui
TARGET = sleep
CONFIG   += console
CONFIG   -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
//main.cpp
#ifdef Q_WS_WIN
#include <windows.h>
#endif
#include <cstdio>
#include <QtCore/QTextStream>
#include <QtCore/QObject>
#include <QtCore/QBasicTimer>
#include <QtCore/QTimer>
#include <QtCore/QCoreApplication>

QTextStream out(stdout);

// this order is important
#define TOKENPASTE2(x,y) x ## y
#define TOKENPASTE(x,y) TOKENPASTE2(x,y)
#define SLEEP(ms) sleep(ms, &SLEEPCLASS::TOKENPASTE(fun, __LINE__)); } void TOKENPASTE(fun, __LINE__)() {

class Object : public QObject
{
    Q_OBJECT
    #define SLEEPCLASS Object // used by the SLEEP macro
public:
    Object() {
        QTimer::singleShot(0, this, SLOT(slot1()));
        periodic.start(100);
        connect(&periodic, SIGNAL(timeout()), SLOT(tick()));
    }
protected slots:
    void slot1() {
        a = 10; // use member variables, not locals
        out << "doing something" << endl;
        sleep(1000, &Object::fun1);
    }
    void tick() {
        out << "*" << endl;
    }

protected:
    void fun1() {
        out << "slept, a=" << a << endl;
        a = 20;
        SLEEP(500);
        out << "slept, a=" << a << endl;
        a = 30;
        SLEEP(250);
        out << "slept, a=" << a << endl;
        a = 40;
        SLEEP(100);
        out << "slept, a=" << a << endl;
        qApp->exit();
    }

private:
    int a; // used in place of automatic variables

private:
    void sleep(int ms, void (Object::*target)()) {
        next = target;
        timer.start(ms, this);
    }
    void timerEvent(QTimerEvent * ev)
    {
        if (ev->timerId() == timer.timerId()) {
            timer.stop(); (this->*next)();
        }
    }
    QTimer periodic;
    QBasicTimer timer;
    void (Object::* next)();
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Object o1;
#ifdef Q_WS_WIN
   timeBeginPeriod(1); // timers will be accurate to 1ms
#endif
    return a.exec();
}

#include "main.moc"
于 2012-05-31T23:25:13.270 回答
3

我同意贾尔夫。我有一个线程充当一种 DBUS 守护进程,需要永远收听消息。有两点要提:

贾夫有

void sleep(int ms) { QThread::sleep(ms); }

但这不是毫秒!QThread::sleep() 需要几秒钟。此外,如果要采用这种方法,他还必须包含 QThread 库,因此像这样进行调用可能更容易:

QThread::sleep(seconds);

直接在代码中。这样就没有额外的头文件了。我跑了这个,它也像 jalf 解释的那样工作。(使调用线程进入睡眠状态。)

于 2014-05-22T10:20:46.740 回答
0

对于 Qt 4.8.0(我正在使用的版本)QThread::sleepQThread::msleepQThread::usleep已公开,因此您可以直接调用它们。在早期的 Qt 版本中,它们是static protected.

例如QThread::sleep(5); // sleep for 5 seconds

于 2018-04-18T03:50:41.853 回答