1、线程使用
在Qt中多线程的处理一般是通过QTread类来控制实现的,这部分的内容与Linux内容强相关,我在学习这一块的时候是没有学习过Linux的,所以我是通过0Linux的基础来写下这部分内容的
API | 说明 |
---|---|
run | 线程入口函数 |
start | 通过调用run开始执行线程,操作系统根据优先级判定,如果线程正在运行,则这个函数相当于没有 |
currentTread | 返回一个指向管理当前执行线程的QTread指针 |
isRunning | 判断线程是否正在运行 |
sleep | 使程序休眠,单位为s,类似的函数:msleep单位为ms,usleep单位为us |
wait | 阻塞线程,与此QTread对象关联的线程已经完成执行或者尚未启动都返回true,如果等待超时,返回false |
terminate | 终止线程执行,通过操作系统的调度决定是否立即终止 |
finished | 线程结束后发出该信号 |
创建一个自定义类timethread,继承自QThread,在ui上创建一个pushbutton和label
timethread.h
class TimeThread : public QThread
{
Q_OBJECT;
public:
TimeThread();
//线程任务函数
void run();
signals:
//声明信号函数
void sendTime(QString Time);
};
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void showTime(QString Time);
void on_pushButton_clicked();
private:
Ui::Widget *ui;
//定义线程对象
TimeThread t;
};
timethread.cpp
void TimeThread::run()
{
while (1) {
QString time = QTime::currentTime().toString("hh::mm::ss");
//发送信号
emit sendTime(time);
sleep(1);
//每一秒发送一次信号
}
}
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//当t每次sendTime的时候,触发showTime
connect(&t,&TimeThread::sendTime,this,&Widget::showTime);
}
Widget::~Widget()
{
delete ui;
}
void Widget::showTime(QString Time)
{
//设置label的内容为时间
ui->label->setText(Time);
}
void Widget::on_pushButton_clicked()
{
//开启线程
t.start();
}
QTread
我们前面也说过,线程函数内部不允许操作ui图形界面,一般是用作数据处理的
connect函数有五个参数,第五个参数就是只有在多线程的时候才有意义,用于指定信号和槽的连接类型,同时影响信号的传递方式和槽函数的执行顺序
参数 | 说明 |
---|---|
Qt::AutoConnection | 根据信号和槽函数所在的线程自动选择连接类型,同一线程使用Qt::DirectConnection,不同线程使用Qt::UniqueConnection |
Qt::DirectConnection | 信号发出时,槽函数会立即在同一线程中执行,适用于信号和槽在同一线程时 |
Qt::QueuedConnection | 信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下一次时间循环时执行,适用于信号和槽不在同一线程 |
Qt::BlockingQueuedConnection | 信号发出时,发送信号的线程会被阻塞,直到槽函数执行完毕,适用于信号和槽不在同一线程 |
Qt::UniqueConnection | 确保信号与槽之间唯一连接关系的标志,可以使用位或操作与上述四种一种连接类型组合使用,可以避免重复连接 |
2、线程安全
(1)互斥锁
互斥锁是一种保护和防止多个线程同时访问同一对象实例的办法,主要通过QMutex类来处理
QMutex
用于保护共享资源的访问,实现线程间的互斥操作,在多线程的环境下,通过互斥锁来控制对共享数据的访问,确保线程安全
QMutex mutex;
mutex.lock();//上锁
//访问共享资源
mutex.unlock();//解锁
QMutexLocker
可以简化对互斥锁的上锁解锁操作,避免忘记解锁导致死锁
QMutex mutex;
{
QMutexLocker locker(&mutex);//作用域内自动上锁
//访问共享资源...
}//作用域结束自动解锁
一个例子
mythread.h
class MyThread : public QThread
{
public:
MyThread(QObject* parent = nullptr);
void run();
private:
//定义全局变量
static QMutex mutex;
static int num;
};
mythread.cpp
void MyThread::run()
{
while (1) {
this->mutex.lock();//锁上
//每有一个线程进来就打印线程以及递增的数字
qDebug() << this <<" : " << this->num++;
this->mutex.unlock();//解锁
QThread::sleep(1);
}
}
在这个代码块中,mutex.lock() 和 mutex.unlock() 手动管理互斥锁,每次打印完信息后立即释放锁,然后进行 QThread::sleep(1),由于锁已经释放,其他线程可以立即进入这段代码,导致线程的执行和打印信息可能是无序的
void MyThread::run()
{
while (1) {
QMutexLocker locker(&mutex);
qDebug() << this <<" : " << this->num++;
QThread::sleep(1);
}
}
在这个代码块中,使用了 QMutexLocker 来管理锁,QMutexLocker 会在它的作用范围内自动锁定 mutex,并在 locker 离开作用域时(即循环的下一次迭代开始时)自动解锁,在这里,QThread::sleep(1) 位于锁的作用范围内,所以整个 sleep 期间锁不会释放,这样可以保证一次只有一个线程在运行这段代码,从而避免线程间的竞争
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
MyThread* t1 = new MyThread(this);
MyThread* t2 = new MyThread(this);
t1->start();
t2->start();
}
QReadWriteLocker、QReadLocker、QWriteLocker
QReadWriteLocker:读写锁类,用于控制读和写的并发访问
QReadLocker:读操作上锁,允许多个线程同时读取共享资源
QWriteLocker:写操作上锁,只允许一个线程写入共享资源
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
QReadLocker locker(&rwLock); //在作⽤域内⾃动上读锁
//读取共享资源
//...
}//在作⽤域结束时⾃动解读锁
//在写操作中使⽤写锁
{
QWriteLocker locker(&rwLock); //在作⽤域内⾃动上写锁
//修改共享资源
//...
}//在作⽤域结束时⾃动解写锁
(2)条件变量
因为在多线程编程中,某些线程需要等待某些条件满足才能执行,此时线程会使用锁的机制来阻塞其他线程,当条件满足时,等待条件的线程将被另一个线程唤醒
QWaitCondition是Qt框架提供的条件变量类,用于线程之间的通信和同步,在某个条件满足时等待或唤醒线程,用于线程的同步和协调
QMutex mutex;
QWaitCondition condition;
//等待线程中
mutex.lock();
//检查条件是否满足,不满足就等待
while (!conditionFullfilled())
{
condition.wait(&mutex);//条件满足释放锁
}
mutex.unlock();
//------------------------------------------------------------------------------------
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();
(3)信号量
QSemaphone是Qt框架提供的计数信号类,用于控制同时访问共享资源的线程数量,用于限制并发线程数量,用于解决一些资源有限的问题
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中也要进行类似操作
今日分享就到这了~