
点击上方蓝字关注我们


多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待阻塞,这样就大大提高了程序的运行效率,也就提高CPU的利用率。一个程序(进程)创建多个线程时,线程与线程之间对共享资源的访问会相互影响,学习线程编程还需要解决线程之间的资源竞争问题。
QT提供了QThread类进行线程管理,QThread采用了独立于平台的方式来管理线程。QThread继承于QObject类,且提供了QMutex、QMutexLocker、QSemaphore相关类来管理线程之间的同步与竞争问题。
11.1 QThread类介绍 要使用QThread类创建线程,则必须子类化QThread类。也就是说必须继承QThread类创建新的派生类。
11.1.1 创建线程 创建线程模型:
//线程的子类化
class Thread : public QThread
{
Q_OBJECT
protected:
void run();
};
void Thread ::run()
{
/*线程执行代码*/
}
|
类成员函数run()是QThread定义的虚函数。run()函数就相当于线程的执行起点。调用start()函数,线程将会进入run()函数执行代码。
线程运行实例:
#include <QApplication>
#include <QDebug>
#include <QObject>
//线程类的子类化
class Thread : public QThread
{
protected:
void run(); //重写run函数
};
//线程执行代码
void Thread::run()
{
uint count=0;
while(1)
{
count++;
sleep(1);
qDebug("count:%d",count);
}
}
int main(int argc, char *argv[])
{
Thread my_thread; //实例化线程
my_thread.start();//开始执行线程
while(1)
{
/*………….*/
}
return 0;
}
|
 ’
图11-1-1 线程执行效果
11.1.2 QThread相关函数介绍 1. 线程的起点
[virtual protected] void QThread::run()
|
run()函数是线程的起点,在调用start()之后,新创建的线程就会调用这个函数,默认实现调用exec(),大多数需要重新实现这个功能,便于管理自己的线程。run()函数返回时,表示该线程已经结束执行。与应用程序(进程)的main函数功能类似。
2. 启动线程
[slot] void QThread::start(Priority priority = InheritPriority)
|
调用start函数后,线程会执行run()函数。在run()函数执行前会发射started()信号,操作系统将根据优先级参数调度线程。如果线程已经在运行,那么调用start这个函数就没有什么作用。优先级参数的效果取决于操作系统的调度策略,不支持线程优先级的系统,优先级将会被忽略。
3. 退出线程相关函数
[slot] void QThread::quit()
void QThread::exit(int returnCode = 0)
[slot] void QThread::terminate()
|
quit()函数:退出线程事件循环,run函数返回0表示成功,相当于调用了QThread::exit(0)。如果线程没有事件循环,该函数将不起作用。
exit()函数:退出线程事件循环。如果线程没有事件循环,该函数将不起作用。
terminate()函数:终止线程,线程可能不会立即终止,这取决于操作系统的调度策略。终止线程后必须调用QThread::wait()函数等待线程结束。注意:此函数使用起来比较危险,因为它可以在任何时候终止线程,线程终止后可能没有机会清理本身,比如:资源解锁等。
4. QThread提供的延时函数
[static] void QThread::usleep(unsigned long usecs) //微秒级延时
[static] void QThread::msleep(unsigned long msecs) //毫秒级延时
[static] void QThread::sleep(unsigned long secs) //秒级延时
|
以上三个为静态函数,可以在不创建线程对象的情况下使用。
示例:
#include <QThread>
int main(int argc, char *argv[])
{
while(1)
{
qDebug()<<"123456";
QThread::sleep(2); //延时2秒
QThread::msleep(2); //延时2毫秒
QThread::usleep(2); //延时2微秒
}
return 0;
}
|
5. 设置线程的堆栈大小
void setStackSize(uint stackSize); //设置堆栈大小
uint stackSize() const; //获取堆栈大小
|
默认情况下线程的堆栈大小由系统自动分配,stackSize函数将返回0。setStackSize函数用于设置堆栈大小。
6. 判断线程的状态
bool QThread::isRunning() const //如果线程正在运行,返回true;否则返回false。
bool QThread::isFinished() const //如果线程已经完成,返回true,否则返回false。
|
示例:
#include <QApplication>
#include <QThread>
class Thread : public QThread
{
protected:
void run(){while(1){}}
};
int main(int argc, char *argv[])
{
Thread my_thread; //实例化
my_thread.start();//开始执行线程
qDebug()<<"线程的运行状态: "<<my_thread.isRunning(); //结果返回:true
qDebug()<<"线程的完成状态: "<<my_thread.isFinished();//结果返回:false
while(1){}
return 0;
}
|
除了可以通过以上函数判断之外,QThread还提供了相关信号来通知线程运行的状态:
void QThread::started() //线程开始前发出的信号
void QThread::finished() //线程终止后发出的信号
|
7. 等待线程结束
bool QThread::wait(unsigned long time = ULONG_MAX)
|
wait函数的形参表示等待的时间,单位为毫秒。
当时间值为默认值(ULONG_MAX)或者为负值时,表示wait函数将永久阻塞,直到线程结束时wait函数才返回。
wait函数返回值说明:
1) 如果线程没有开始执行或者已经结束,wait函数返回true。
2) 如果线程没有结束,wait函数返回false。
示例:
#include <QApplication>
#include <QThread>
//线程类的子类化
class Thread : public QThread
{
uint count=0;
protected:
void run()
{
while(1)
{
count++;
if(count>5)
{
break;
}
sleep(1);
qDebug("count: %d",count);
}
}
};
int main(int argc, char *argv[])
{
Thread my_thread; //实例化
my_thread.start();//开始执行线程
qDebug()<<"线程返回状态:"<<my_thread.wait(-1); //等待线程结束
while(1){};
return 0;
}
|
运行结果:

图11-1-2 wait函数运行结果
8. 返回系统运行线程的理想数量
int QThread::idealThreadCount()
|
如果系统的CPU核心检测不到,将返回1。
9. 返回当前正在运行的线程指针
[static] QThread *QThread::currentThread()
|
10. 线程优先级设置
void QThread::setPriority(Priority priority) //设置优先级
Priority QThread::priority() const //获取优先级
|
优先级设置表:
QThread::IdlePriority
| 线程处于不运行状态
| QThread::LowestPriority
| 比低优先级更底
| QThread::LowPriority
| 比正常优先级底
| QThread::NormalPriority
| 比高优先级底
| QThread::HighPriority
| 比最高优先级底
| QThread::HighestPriority
| 最高优先级
| QThread::TimeCriticalPriority
| 尽量依赖平台变化调整
| QThread::InheritPriority
| 启动线程默认
|
11.1.3 多线程编程实例 下面例子介绍如何使用一个简单的多线程例子,以下例子实现的模型类似与网络中服务器与客户端的通信模型。
先看运行例子程序的效果:

图11-1-3 多线程运行效果
点击”创建线程”按钮实现创建新线程,创建成功后线程不会运行。需要点击”开始运行”按钮运行创建的线程。下面有两个标签控件,用于显示线程创建的数量和正在运行的数量。最后两个按钮分别用于停止所有线程和删除所有创建的线程。创建线程的按钮可以重复点击,实现创建多个线程。
下面展示工程具体的创建步骤(配套代码编号CH11-1):
1. widget.ui文件

图11-1-4 UI设计界面
Widget窗口整体采用了垂直布局。包含的控件都采用了水平布局。
2. widget.h文件代码
widget.h文件中主要继承了QThread线程类。
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThread>
#include <QMutex>
#include <QSemaphore>
#include <QMutexLocker>
#include <QDebug>
#include <QObject>
#include <QWidget>
namespace Ui {
class Widget;
}
//线程类的子类化
class Thread : public QThread
{
protected:
void run();
};
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
QList<Thread*> Thread_list; //存放线程
private slots:
void on_pushButton_new_clicked();
void on_pushButton_start_clicked();
void on_pushButton_stop_clicked();
void on_pushButton_del_thread_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
3. widget.cpp文件代码
widget.cpp代码主要部分是实现界面4个按钮的功能。
(1)“创建线程”按钮槽函数代码
//创建线程
void Widget::on_pushButton_new_clicked()
{
Thread_list.append(new Thread); //创建线程
ui->label_number->setNum(Thread_list.count()); //显示创建的数量
}
|
每创建一个新的线程,就将线程的地址保存到Thread_list列表中。并显示当前创建线程的总数量。
(2) “开始运行”按钮槽函数代码
//开始运行
void Widget::on_pushButton_start_clicked()
{
for(int i=0;i<Thread_list.count();i++)
{
if(!Thread_list.at(i)->isRunning()) //判断线程运行状态
{
Thread_list.at(i)->start(); //开始运行线程
}
}
ui->label_number_start_unmber->setNum(Thread_list.count()); //显示运行的数量
}
|
运行线程之前,先挨个判断线程的状态。如果线程处于未运行状态,就开始运行线程。然后将当前线程运行的总数量显示在标签控件上。
(3)“停止运行”按钮槽函数代码
//停止运行
void Widget::on_pushButton_stop_clicked()
{
for(int i=0;i<Thread_list.count();i++)
{
if(Thread_list.at(i)->isRunning()) //判断线程运行状态
{
Thread_list.at(i)->terminate(); //终止线程
Thread_list.at(i)->wait(); //等待线程结束
}
}
}
|
停止线程之前,先挨个判断线程的运行状态。如果线程处于运行状态,就终止运行。
(4)“销毁线程”按钮槽函数代码
//销毁线程
void Widget::on_pushButton_del_thread_clicked()
{
for(int i=0;i<Thread_list.count();i++)
{
if(Thread_list.at(i)->isRunning()) //判断线程运行状态
{
Thread_list.at(i)->terminate(); //终止线程
Thread_list.at(i)->wait(); //等待线程结束
delete Thread_list.at(i); //删除线程
Thread_list.removeAt(i); //删除列表中的节点
}
else
{
delete Thread_list.at(i); //删除线程
Thread_list.removeAt(i); //删除列表中的节点
}
}
ui->label_number_start_unmber->setNum(Thread_list.count()); //显示运行的数量
ui->label_number->setNum(Thread_list.count()); //显示创建的数量
}
|
销毁线程前需要先停止线程,线程销毁后,也需要将Thread_list列表对应的节点删除掉。
11.1.4 子线程与主线程通信实例 众所周知,QT的主线程必须保持畅通,才能刷新UI界面。如果涉及到使用子线程更新UI上的控件时就需要自定义信号与槽函数。比如:在网络编程中,可以在子线中接收网络数据,然后发送信号给主线程更新界面。
下面代码实现子线程每一秒钟通过信号给主线程传递一个数字,在主线程的UI界面进行显示。运行效果如下:(配套代码编号CH11-2)


1. “thread.h”文件代码示例
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
class Thread : public QThread
{
Q_OBJECT
public:
Thread();
signals:
void TxData(int data); //信号声明
protected:
void run(); //线程执行的函数
};
#endif // THREAD_H
|
2. “thread.cpp”文件代码示例
#include "thread.h"
Thread::Thread()
{
}
void Thread::run()
{
int count=0;
while(1)
{
count++;
emit TxData(count);
this->msleep(1000);
}
}
|
3. “widget.h”文件代码示例
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "thread.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
Thread *thread;
//声明槽函数
public slots:
void RxData(int data);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
4. “widget.cpp”文件代码示例
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{
ui->setupUi(this);
thread=new Thread;
//关联线程发送的数据信号。
//注意:关联信号时,信号和槽函数的形参只能填类型,不能填具体的变量。
connect(thread, SIGNAL(TxData(int)), this, SLOT(RxData(int)));
thread->start();
}
Widget::~Widget()
{
delete ui;
}
void Widget::RxData(int data)
{
//更新显示UI界面
ui->lcdNumber->display(data);
}
|
11.1.5 线程与主线程UI界面交互(友元类) 1. widget.cpp 文件
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
thread_flag=0; //默认线程停止
thread.show_ui=this; //给值
}
Widget::~Widget()
{
delete ui;
}
//开始执行线程
void Widget::on_pushButton_startthread_clicked()
{
thread_flag=!thread_flag;
if(thread_flag)
{
thread.count=0; //清空计数器
thread.start(); //开始执行线程
ui->pushButton_startthread->setText("停止执行线程");
}
else
{
thread.terminate();//终止线程的执行。线程可能会或可能不会立即被终止,这取决于操作系统的调度策略。终止后需要调用QThread::wait()()。
thread.wait();
ui->pushButton_startthread->setText("开始执行线程");
}
}
//线程执行起始点
void Thread::run()
{
/* 线程的相关代码 */
while(1)
{
count++;
qDebug()<<count;
this->msleep(500); //延时
//UI界面显示进度
show_ui->ui->lcdNumber_thread1->display(count);
}
}
|
2. widget.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThread>
#include <QDebug>
class Widget;
//线程类的子类化
class Thread : public QThread
{
public:
int count;
Widget *show_ui; //指向widget的指针
protected:
void run(); //线程执行的函数
};
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
Thread thread; //实例化一个线程对象
bool thread_flag; //控制线程开始停止
friend class Thread; //声明友元类
private slots:
void on_pushButton_startthread_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
|
11.2 多线程资源保护 为了保证多线程能够正常运行,QT提供了一系列保护措施。具体可以分为互斥和同步这两种关系。下面的表格中列出了线程互斥和同步常用的类。
类名称
| 功能说明
| QMutex
| 互斥体。保证线程之间的同步,有序的访问共享资源。
| QSemaphore
| 信号量。通过count访问计数来同步线程。
| QMutexLocker
| 简化lock和unlock Mutex
| QReadWriteLock
| 控制读写操作
| QReadLocker
| 控制读操作
| QWaitCondition
| 提供一个条件变量同步线程
|
11.2.1 互斥体 QMutex类是对互斥体的处理。QMutex的目的是保护一段临界区代码,确保这个临界区内的代码每次只能被一个线程所访问。QMutexLocker类与QMutex类功能类似,都属于互斥体。
互斥体相关头文件:
#include <QMutex>
#include <QMutexLocker>
|
1. 构造QMutex类
QMutex(RecursionMode mode = NonRecursive)
|
2. 上锁
void QMutex::lock() //阻塞方式获取锁
bool QMutex::tryLock(int timeout = 0) //非阻塞方式获取锁
|
如果QMutex在之前已经被其他线程抢先一步上锁,那么调用lock当前线程将进入阻塞状态。一直需要等到其他线程解锁之后才能返回。
tryLock函数的形参可以填入时间值,分为3种情况:
timeout = 0:当访问的互斥体已经被锁定,tryLock函数将返回true,否则返回false。
timeout <0 :当访问的互斥体已经被锁定时,与调用lock函数效果一样,会一直阻塞。
timeout >0 :如果访问的互斥体已经被锁定,tryLock将阻塞timeout时间段后返回,否则会立即返回。
3. 解锁
如何之前调用了lock()或者tryLock函数进行了上锁,就需要调用unlock解锁。
互斥体的基本使用步骤:创建锁-->上锁-->解锁
//线程执行函数
QMutex mutex; //构造互斥体
void Thread::run()
{
uint count=0;
while(1)
{
mutex.lock();//上锁
/*临界区*/
qDebug()<<count++;
mutex.unlock(); //解锁
}
}
|
以上代码片段是在11.1.2小节的示例代码上加了一个互斥体,为了保护打印函数同时只能被一个线程所调用。
运行效果:

图11-2-1 互斥体运行实例
从以上截图中可以发现,加了互斥体以后四个线程同时运行,打印的数据都是按照顺序执行的。
11.2.2 信号量 信号量可以理解为是对互斥体的扩展,互斥体只能锁定一次,也就是说只能保证资源只能同时被一个线程所访问。而信号量可以锁定多次,可以保证资源同时可以被1个进程、2个进程或者2个以上的进程同时访问。根据信号量这个特性,它一般用来保护一定数量的同种资源。信号量经典的使用案例是控制生产者与消费者之间的关系。
以火车票售票系统为例:假设"深圳->成都"的火车票只剩下3张。这时如果同时来了5个人进行购票,售票系统只允许其中3个人能够买到票。如果之前购买的人退了2张票,售票系统又允许等待购票的一批人中的2个人能够买票。在这个售票系统中,车票数量是公共资源,每一个购票者就相当于一个线程,而售票系统本身就相当于信号量的作用。
头文件:
1. 构造信号量
QSemaphore::QSemaphore(int n = 0)
|
n表示构造的信号量数量。信号量n的值大于0表示可用,小于0表示不可用。
2. 阻塞方式获取信号量
void QSemaphore::acquire(int n = 1)
|
Acquire函数用于获取指定数量的信号量,默认n的值为1,表示获取一个信号量。如果信号量获取成功,acquire函数会立即返回,否则acquire函数将会阻塞,直到其他进程释放了信号量才会返回。获取信号量就是对信号量的值做减法操作。
3. 非阻塞方式获取信号量
bool QSemaphore::tryAcquire(int n = 1)
|
n表示获取的信号量数量,如果获取成功返回true,否则返回false。
示例:
QSemaphore sem(5); // 创建5个信号量
sem.tryAcquire(25); // 想要获取25个信号量,返回结果为false
sem.tryAcquire(3); // 想要获取3个信号量, 返回结果为true
|
4. 释放信号量
void QSemaphore::release(int n = 1)
|
释放信号量就是创造信号量,n表示创建的信号量数量。如果n的值传入5,表示增加5个信号量值。
5. 返回当前可用的信号量
int QSemaphore::available() const
|
示例:
QSemaphore sem(5);
qDebug()<<sem.available(); //返回为5
|
信号量的使用的步骤与互斥体一样,如果信号量的值设置为1,功能就和互斥体没有区别。
 技术合作与咨询
QQ:1126626497 关注我长按二维码可识别微信号:xl1126626497

---------------------------------------------------------------------------------------------------------------------- 我们尊重原创,也注重分享,文章来源于微信公众号:DS小龙哥 嵌入式技术资讯,建议关注公众号查看原文。如若侵权请联系qter@qter.org。 ----------------------------------------------------------------------------------------------------------------------
|