如何使用多线程制作一个简单的 Qt 控制台应用程序?
Posted
技术标签:
【中文标题】如何使用多线程制作一个简单的 Qt 控制台应用程序?【英文标题】:Howto make a simple Qt console application with multithreading? 【发布时间】:2013-09-06 11:08:26 【问题描述】:我很难理解如何使最简单的工作 多线程 Qt 控制台 应用程序。
我已经阅读了大量关于如何使用 QThread 类的资料。 他们中的一些人说是 QThread 的子类,其他人说使用 QThread 的工作类包装器。
经过多次尝试和重试后,我仍然无法使多线程工作 Qt 控制台应用程序。
现在我不需要任何花哨的 Qt Gui。
有人可以帮我填写示例代码的线程部分吗? 它一次只从文本文件中读取一行,其想法是目前不忙的每个线程(我想使用 4 个线程)将尽快使用 std::cout 将该行打印到标准输出。只需打印它,暂时没有其他花哨的处理东西可以让我保持简单。
#include <QCoreApplication>
#include <QFile>
#include <iostream>
/* QThread stuff here */
/* Don't know how to make it */
int main(int argc, char *argv[])
QCoreApplication a(argc, argv);
/* Create four instances of threads here and
put them to wait readed lines */
QFile file("file.txt");
file.open(QIODevice::ReadOnly | QIODevice::Text);
while(!file.atEnd())
/* Read lines here but where should they be saved?
Into a global variable like QList<QByteArray> list ?
So that each thread can read them from there or where ???? */
??? = file.readLine();
file.close();
a.exit();
【问题讨论】:
在多线程应用程序中,您不会使用全局变量。这不安全。 为什么你仍然希望它是多线程的?你的目标是什么?当然,它是否是 GUI 应用程序并不重要。 好吧,我的最终目标是制作一个简单的控制台网络客户端应用程序,其中包含多线程 waitForConnected() TcpSocket 调用。但现在我只需要知道如何使用简单的示例代码进行任何多线程处理。是的,那些 waitForConnected() 调用需要每个不同的主机名,我将从主线程中的文本文件中读取它 提供一些代码,您尝试在其中进行一些多线程处理。从不同的线程使用 std::cout 是个坏主意。 @fiscblog 那么如果处理共享资源(在这种情况下为文本文件),不同的线程如何通信和传递状态信息。我对多线程一无所知 【参考方案1】:将功能放入 QObject 的插槽中
重点是:
请记住,每个QObject
都有一个特定的thread()
,它“生活”在其中。每个线程都可以在那里运行一个事件循环。这个事件循环会将发送的事件传递给在这个线程中“存活”的对象。
不要从QThread
派生。开始库存QThreads
。他们将在QThread::run()
的默认实现中启动一个偶数事件循环。
在插槽(或Q_INVOKABLE
)方法中实现您的功能。该类显然必须从 QObject
派生。
当您将信号(使用信号槽连接,而不是直接)发送到 #3 中的槽时,就会发生魔法。从运行在 GUI 线程中的通知程序到被通知对象的连接是使用 Qt::QueuedConnection
自动完成的,因为发送方和接收方对象位于不同的线程中。
向这样的对象发送信号会导致将事件发送到对象所在线程的事件队列。事件循环的事件分派器将选择这些事件并调用适当的插槽。这就是 Qt 的强大之处——可以为您做很多有用的事情。
请注意,没有“当前繁忙”线程的概念。线程执行住在那里的对象的短槽。如果您想在“忙”和“不忙”状态之间移动线程,那么您需要额外的代码。
实现它的另一种方法是从QRunnable
派生并使用QThreadPool
。这是另一个答案。
main.cpp
#include <QCoreApplication>
#include <QTextStream>
#include <QThread>
#include <QFile>
#include <cstdio>
class Notified : public QObject
Q_OBJECT
QTextStream m_out;
public:
Q_SLOT void notify(const QString & text)
m_out << "(" << this << ") " << text << endl;
Notified(QObject *parent = 0) : QObject(parent), m_out(stdout)
;
class Notifier : public QObject
Q_OBJECT
Q_SIGNAL void notification(const QString &);
public:
Notifier(QObject *parent = 0) : QObject(parent)
void notifyLines(const QString & filePath)
QFile file(filePath);
file.open(QIODevice::ReadOnly | QIODevice::Text);
while (! file.atEnd())
emit notification(file.readLine());
file.close();
;
int main(int argc, char *argv[])
QCoreApplication a(argc, argv);
QObjectList notifieds;
QList<QThread*> threads;
Notifier notifier;
for (int i = 0; i < 4; ++i)
QThread * thread = new QThread(&a); // thread owned by the application object
Notified * notified = new Notified; // can't have an owner before it's moved to another thread
notified->moveToThread(thread);
thread->start();
notifieds << notified;
threads << thread;
notified->connect(¬ifier, SIGNAL(notification(QString)), SLOT(notify(QString)));
notifier.notifyLines("file.txt");
foreach (QThread *thread, threads)
thread->quit();
thread->wait();
foreach (QObject *notified, notifieds) delete notified;
a.exit();
#include "main.moc"
【讨论】:
【参考方案2】:出于您的目的,我根本不会使用 QThread
,而是使用来自 QtConcurrent
的类。
一些简单的东西(假设你有 C++11):
while(!file.atEnd())
QString line = file.readLine();
QtConcurrent::run([line]
qDebug() << line;
);
虽然我仍然不确定这会给你带来什么。
【讨论】:
严格来说是 C++11 吗?我需要什么版本的 Qt/QtCreator/gcc(随便)? 至少从 Qt 4.7 开始就有了。 C++11 部分是 lambda 表达式,但您也可以使用命名函数来执行此操作,因此 C++11 不是必需的,但对于此类事情更具可读性。 我可以再问一个问题。 QtConcurrent::run() 在那个 while 循环中创建了多少个线程?我的意思是,是否有一些内部限制或什么?否则,如果我要从文本文件中读取 10 000 行,那么如果程序也在创建 10 000 个线程,我会感到有点不舒服 :) 它在 QThreadPool 下使用,默认情况下将使用处理器数作为线程数。【参考方案3】:下面的链接可能对您在 Qt 中使用线程相关的信息有用
http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
如果您只想以异步方式完成文件读取,Qt 有几种替代技术,例如 QtConcurrent。
http://qt-project.org/doc/qt-4.8/threads-qtconcurrent.html
这里有一些示例代码可以帮助您使用 QtConcurrent
在单独的线程中运行函数
extern QString aFunction();
QFuture<void> future = QtConcurrent::run(aFunction);
aFunction 应该包含读取文件的代码。
可以通过以下方式返回读取的数据
QFuture<QString> future = QtConcurrent::run(aFunction);
...
QString result = future.result();
请注意,QFuture::result() 函数会阻塞并等待结果可用。当函数执行完毕且结果可用时,使用 QFutureWatcher 获取通知。
希望这会有所帮助。以上代码均取自Qt文档。
【讨论】:
I have read tons of stuff on how to use QThread class
,他说。
@user1764879:是的,我之前已经阅读过,woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 和 blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong,但仍然不确定如何正确使用它们。要么我以错误的方式开始它们,要么我的包装类完全错误。并且 qtconcurrent 文档没有任何示例代码:(
@user1764879:好的。谢谢你。我认为您的示例与 inflagranti 之前给出的答案相结合,将把我推向正确的方向。因此,我将只在 QtConcurrent:run() 在 while 循环中调用的某个回调函数中进行处理(在这种情况下将行打印到标准输出),就这样吧?
不需要使用while循环..将函数名作为参数传递给运行函数并将从文件中读取的数据作为字符串返回(它可以是任何其他类型,也可以是QList将功能放入 QRunnable
也许最接近您明确需求的解决方案将使用QThreadPool
。它做你想做的事:它从它的池中挑选一个不忙的线程,并在那里运行工人。如果没有空闲线程,它会将可运行线程添加到运行队列中,每次空闲线程可用时都会耗尽。
但请注意,您明确希望拥有一个线程状态(即忙/非忙)与需要在尝试每个新密码之前等待回复的网络渗透系统完全不匹配。你会想要它基于QObjects
。我将修改我的其他答案,以展示您在管理网络连接时如何做到这一点。将线程浪费在忙于等待网络答案上是非常非常浪费的。你确实不想要那样做。效果会很差。
您的应用程序受 I/O 限制,几乎可以在一个线程上运行,而不会造成过多的性能损失。只有当您拥有庞大的网络管道并且同时测试数万个帐户时,您才需要多个线程。我是认真的。
#include <QCoreApplication>
#include <QTextStream>
#include <QRunnable>
#include <QThreadPool>
#include <QFile>
#include <cstdio>
class Data : public QString
public:
Data(const QString & str) : QString(str)
;
class Worker : public QRunnable
QTextStream m_out;
Data m_data;
public:
void run()
// Let's pretend we do something serious with our data here
m_out << "(" << this << ") " << m_data << endl;
Worker(const Data & data) : m_out(stdout), m_data(data)
;
int main(int argc, char *argv[])
QCoreApplication a(argc, argv);
QThreadPool * pool = QThreadPool::globalInstance();
QFile file("file.txt");
file.open(QIODevice::ReadOnly | QIODevice::Text);
while (! file.atEnd())
const Data data(file.readLine());
Worker * worker = new Worker(data);
pool->start(worker);
file.close();
pool->waitForDone();
【讨论】:
嗯...这个 QRunnable 看起来很简单,但是那些线程是立即启动的吗?我的意思是,如果我读取例如 10 000 行文件,那么我想尽快启动这些线程,而不是在读取所有 10 000 行之后。我什至可以有一个 700 万行的文本文件,所以我不能等待它全部读取然后启动线程 线程在您调用pool->start()
时立即启动。当然线程数是有限的,请参阅QThreadPool
文档。无论如何,您不希望线程数超过 2 倍内核数。 QThreadPool
负责处理。
好的,非常感谢。如果我也想在那个 Worker 类中做一些 TcpSocket 连接怎么办?例如,我将如何处理 connect(socket,SIGNAL(connected()),this,SLOT(doSomeStuffWhenConnected())) ?
如果你想在那个 Worker 线程中处理连接怎么办?好吧,您可能不想这样做,因为它不会给您带来任何优势。迭代(强制布鲁斯)密码完全受 I/O 限制。没有计算可言。它可以在一个线程中运行,除非你有一个巨大的网络连接,或者在数据中心的服务器上运行。如果你真的不知道如何处理信号和槽,你需要阅读 Qt 教程。有关数十个示例,请参见我的其他答案。网络的东西也是。以上是关于如何使用多线程制作一个简单的 Qt 控制台应用程序?的主要内容,如果未能解决你的问题,请参考以下文章