线程可以看作是cpu运行的基本的基本单位,进程可以看作是运行资源的基本单位。程序的一次执行就可以看作是一个进程。进程中又包含了许多的线程,进程之间的内存不可以共享,线程之间共享进程的内存。在程序中,线程是独立的、并发的执行流。
多线程一般用于在多任务分发处理过程中,为了任务的调度,并发执行而采用的技术,创建线程执行任务可以提高程序执行性能,提高CPU使用效率,而且单CPU多线程是时间轮片的切换,多CPU可以真正的做到多CPU同时工作。
1、程序地址空间共享
2、代码段共享,数据不能共享
3、多线程单核会有线程切换,多核没有仅仅是数据处理就可以
4、用多线程既能快速处理数据又能快速进行计算
5、在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6、计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现.
多线程有优点,同时也具有缺点,大体表现在以下几个方面:
自定义线程类,需要添加
代码示例:
class WorkerThread : public QThread
{
Q_OBJECT
void run() override {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &s);
};
void MyObject::startWorkInAThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
继承QObject自定义类,在需要使用线程的类头文件中要加上QThread头文件,并创建线程成员对象或者指针,然后在调用的地方创建线程对象,并连接响应信号,并使用moveThread函数将当前对象移动到线程中使用,然后调用开始启动,此方法Qt比较推荐的一种。
示例代码:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
QRunnable是所有可执行对象的基类。我们可以继承QRunnable,并重写虚函数void QRunnable::run () ,来实现线程的创建,但是一般这种都是配合线程池来使用的类,QThreadPool构建的参数就是QRunnable,设置setAutoDelete (true),执行完成自动删除。
示例1代码:
#include
#include
#include
#include
class CurrentTask : public QRunnable
{
// 线程执行任务:每间隔1s打印出线程的信息
void run()
{
for (int nCount = 0; nCount < 5; nCount++)
{
qDebug() << QThread::currentThread();
QThread::msleep(1500);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThreadPool threadpool; // 构建一个本地的线程池
threadpool.setMaxThreadCount(3); // 线程池中最大的线程数
for (int nNum = 0; nNum < 150; nNum++)
{
CurrentTask *task = new CurrentTask(this); // 循环构建可在线程池中运行的任务
task->setAutoDelete(true);
threadpool.start(task); //线程池分配一个线程运行该任务
QThread::msleep(1500);
}
return a.exec();
}
示例1代码执行实现简单的任务,如果需要使用信号与槽通信,则需要继承QObject,
如示例2:
#include
#include
#include
#include
class CurrentTask: public QObject, public QRunnable
{
Q_OBJECT
// 自定义信号
signals:
void finished();
public:
void run() {
qDebug() << "Hello Thread : " << QThread::currentThreadId();
emit finished();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThreadPool threadpool; // 构建一个本地的线程池
threadpool.setMaxThreadCount(3); // 线程池中最大的线程数
for (int nNum = 0; nNum < 150; nNum++)
{
CurrentTask *task = new CurrentTask(this); // 循环构建可在线程池中运行的任务
task->setAutoDelete(true);
threadpool.start(task); //线程池分配一个线程运行该任务
QThread::msleep(1500);
}
return a.exec();
}
注意:如果要在run函数里需要实现tcpsocket的收发需要加入QEventLoop循环,否则将不会触发,自动断开连接,当你任务完成可以写退出循环的槽连接quit()退出事件。
QtConcurrent命名空间提供了很多方法可以实现并发编程,需要加入头文件#include
示例代码:
void func(QString str)
{
qDebug() << __FUNCTION__ << str << QThread::currentThreadId() << QThread::currentThread();
}
void Widget::onBtnWriteClicked()
{
QFuture
f1.waitForFinished();
}
Qt中提供了一个便捷的函数QMetaObject::invokeMethod,方便我们异步调用,类似线程的作用。
函数原型:
bool QMetaObject::invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType type,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(nullptr),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
此函数用于调用对象的成员(信号或插槽)。如果可以调用成员,则返回true。如果没有此类成员或参数不匹配,则返回false。
QMetaObject::invokeMethod除上文这个函数以外还有5个重载函数,这里不再赘述。
参数说明:
obj:被调用对象的指针
member:成员方法的名称
type:连接方式,默认值为 Qt::AutoConnection
Qt::DirectConnection,则会立即调用该成员。(同步调用)
Qt::QueuedConnection,则会发送一个QEvent,并在应用程序进入主事件循环后立即调用该成员。(异步调用)
Qt::BlockingQueuedConnection,则将以与Qt :: QueuedConnection相同的方式调用该方法,除了当前线程将阻塞直到事件被传递。使用此连接类型在同一线程中的对象之间进行通信将导致死锁。(异步调用)
Qt::AutoConnection,则如果obj与调用者位于同一个线程中,则会同步调用该成员; 否则它将异步调用该成员。
ret:接收被调用函数的返回值
val0~val9:传入被调用函数的参数,最多十个参数
注:必须要使用Q_RETURN_ARG()宏来封装函数返回值、Q_ARG()宏来封装函数参数。
示例代码:
bool result;
//同步调用
QMetaObject::invokeMethod(obj, "func", Qt::DirectConnection,
Q_RETURN_ARG(bool, result),
Q_ARG(QString, "test"),
Q_ARG(int, 100);
//异步调用
QMetaObject::invokeMethod(obj, "func", Qt::QueuedConnection,
Q_ARG(QString, "test"),
Q_ARG(int, 100);
注意:使用Qt::QueuedConnection异步调用,将无法获取返回值,因为此连接方式只是负责把事件交给事件队列,然后立刻返回,所以,函数返回值就无法确定了。但,我们可以使用上文提及的Qt:: DirectConnection连接方式,这个连接方式会阻塞发射信号的线程一直等到队列连接槽返回后,才会恢复阻塞,这样就可以保证我们能得到函数的返回值。