文章目录
一、互斥锁、QMutex(个人理解)
“互斥锁”,顾名思义,是可以上锁和解锁的。Qt中互斥锁类为QMutex(在C语言和C++也有互斥锁),该类提供了对应的上锁(lock())和解锁(unlock())等函数。
当多个线程同时运行时,各个线程是独立且互不影响的,那么要如何实现某一线程运行时其他线程不运行呢,没错,可以使用互斥锁。
当多个线程同时使用同一个互斥锁时,首先抢到互斥锁的线程将互斥锁上锁,在互斥锁未解锁时,其他线程是不能使用该互斥锁的,所以在使用互斥锁时,在结束使用的位置一定要解锁(因为单纯的QMutex使用起来容易出现问题,Qt还提供了QMutexLocker类,链接在文章末尾)。
二、条件变量、QWaitCondition(个人理解)
“条件变量”通常是和互斥锁配合使用。Qt中等待条件类为QWaitCondition(在C语言和C++也是有对应的条件变量),该类提供了对应的等待(wait())和唤醒(wakeAll())等函数
该类的作用为将某线程的阻塞并暂时释放该线程的锁或唤醒正在阻塞的线程;比如说,多线程运行时的顺序是不同的,要限保证其顺序运行则可以使用条件变量。
三、QMutex实现多线程循环输出ABC
1、多线程循环输出ABC示例图
下图为循环输出ABC示例图,后面的数值代表输出线程的线程ID
2、多线程循环输出ABC源码(详细注释)
作者使用的是自定义线程类实现的输出,具体代码如下:
CThread.h
#ifndef CTHREAD_H
#define CTHREAD_H
#include <QObject>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
class CThread : public QThread
{
Q_OBJECT
public:
explicit CThread(QObject *parent = nullptr);
~CThread();
void run();
public:
void setFlag(char flag);
void setNextCondition(QWaitCondition *nextCondition); //设置下次应运行线程的线程锁
QWaitCondition *currentCondition() const; //获取当前线程锁函数,用于其他线程设置m_nextMutex
private:
QMutex * m_mutex; //定义一个线程锁变量
QWaitCondition * m_currentCondition; //定义一个当前线程的QWaitCondition变量(条件变量)
QWaitCondition * m_nextCondition; //定义一个用于存放下一个运行线程QWaitCondition变量
char m_flag; //定义一个标识符,用于输出
};
#endif // CTHREAD_H
CThread.cpp
#include "CThread.h"
#include <QDebug>
CThread::CThread(QObject *parent)
: QThread(parent)
{
m_mutex = new QMutex; //new当前线程的线程锁
m_currentCondition = new QWaitCondition; //new当前线程的条件变量
}
CThread::~CThread()
{
delete m_currentCondition;
delete m_mutex;
}
void CThread::run()
{
//循环输出八次
for(int index = 0; index != 8; ++index)
{
//将线程锁锁住
m_mutex->lock();
//阻塞并暂时释放指定的线程锁(等待wakeAll函数或wakeOne函数将其唤醒)
m_currentCondition->wait(m_mutex);
//输出当前线程的标识符和线程ID
qDebug() << m_flag << QThread::currentThreadId();
QThread::usleep(300000); //使线程睡眠一段时间(单位:微秒)
//线程锁解锁
m_mutex->unlock();
//唤醒m_nextCondition中所有正在等待的线程
//wakeOne函数也能唤醒等待的线程,不过好像不能指定,除非有不同的条件变量
//(wakeOne唤醒的线程取决于操作系统的调度策略,无法控制或预测)
m_nextCondition->wakeAll();
}
}
void CThread::setFlag(char flag)
{
m_flag = flag;
}
void CThread::setNextCondition(QWaitCondition *nextCondition)
{
m_nextCondition = nextCondition;
}
QWaitCondition *CThread::currentCondition() const
{
return m_currentCondition;
}
CMainWindow.h
#ifndef CMAINWINDOW_H
#define CMAINWINDOW_H
#include <QMainWindow>
#include "CThread.h"
namespace Ui {
class CMainWindow;
}
class CMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit CMainWindow(QWidget *parent = 0);
~CMainWindow();
private slots:
void on_startBtn_clicked(); //按钮槽函数(环形唤醒)
private:
Ui::CMainWindow * ui;
QList<CThread *> m_threadList; //线程指针容器
};
#endif // CMAINWINDOW_H
CMainWindow.cpp
#include "CMainWindow.h"
#include "ui_CMainWindow.h"
#include <QDebug>
CMainWindow::CMainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::CMainWindow)
{
ui->setupUi(this);
//使用循环为线程链表添加三个线程并运行
for(int index = 0; index != 3; ++index)
{
m_threadList.append(new CThread);
m_threadList[index]->setFlag(65 + index); //设置标识符
m_threadList[index]->start(); //启动线程
}
//将获各个线程的m_currentCondition设置到对应的存储位置中
//!这里可以如此理解 (0->1:代表0唤醒1)
//! 0->1,1->2,2->0
//! 如此看来则形成了环状的唤醒关系
m_threadList[0]->setNextCondition(m_threadList[1]->currentCondition());
m_threadList[1]->setNextCondition(m_threadList[2]->currentCondition());
m_threadList[2]->setNextCondition(m_threadList[0]->currentCondition());
}
CMainWindow::~CMainWindow()
{
foreach (CThread *thread, m_threadList)
{
thread->quit();
thread->wait(1);
delete thread;
}
delete ui;
}
void CMainWindow::on_startBtn_clicked()
{
//如果观察了自定义线程类,会发现线程启动过后都会阻塞在wait函数的位置
//所以我们需要自己唤醒某一个线程让环形的唤醒关系跑起来
m_threadList[0]->currentCondition()->wakeAll();
}
总结
因为QMutex上锁和解锁的情况,编程中一定要小心(有兴趣也可以了解 QMutexLocker)
相关文章
启动QThread线程的两种方法(含源码+注释)
Qt互斥锁(QMutex)的使用、QMutexLocker的使用(含源码+注释)
QSemaphore的使用+QSemaphore实现循环输出ABC(含源码+注释)
QRunnable线程、QThreadPool(线程池)的使用(含源码+注释)
Qt读写锁(QReadWriteLock)的使用、读写锁的验证(含源码+注释)
Qt读写锁(QWriteLocker、QReadLocker)的理解和使用(含部分源码)
Qt之线程运行指定函数(含源码+注释,优化速率)
友情提示——哪里看不懂可私哦,让我们一起互相进步吧
(创作不易,请留下一个免费的赞叭 谢谢 ^o^/)
注:文章为作者编程过程中所遇到的问题和总结,内容仅供参考,若有错误欢迎指出。
注:如有侵权,请联系作者删除