互斥锁
互斥锁介绍:【C++并发编程】(三)互斥锁:std::mutex。原理都一样,这里就不赘述了。
QMutex
是 Qt 框架中提供的一个互斥锁类,主要包括以下成员函数:
- lock():试图锁定互斥量。如果另一个线程已经锁定了这个互斥量,调用线程将被阻塞,直到那个线程解锁。
- unlock():解锁互斥量,允许其他线程锁定它。
- tryLock() :尝试锁定互斥量,不阻塞调用线程。如果互斥量被锁定,函数返回
false
;如果成功锁定,返回true
。 - tryLock(int timeout) :尝试在指定的毫秒数内锁定互斥量。如果超时仍未锁定,返回
false
;如果成功锁定,返回true
。 - locked() :查询互斥量当前是否被锁定。如果被锁定,返回
true
;否则返回false
。
在简单的函数中,可以直接使用 QMutex 的 lock()
和 unlock()
成员函数。但在复杂的函数中,使用 QMutexLocker
自动管理互斥锁更为安全和方便。QMutexLocker 与C++标准中std::lock_guard
的用法差不多,而且也是基于 RAII(Resource Acquisition Is Initialization)机制的,在构造时自动锁定互斥量,在析构时自动解锁。
下面给出示例代码:
https://github.com/BinaryAI-1024/QtStudy/tree/master/thread/mutex
cpp
//myworker.h
#ifndef MYWORKER_H
#define MYWORKER_H
#include <QObject>
#include <QMutex>
#include <QDebug>
class MyWorker : public QObject
{
Q_OBJECT
signals:
void finished();
public:
explicit MyWorker(QObject *parent = nullptr);
// 获取 counter 的值
static int getCounter();
public slots:
void doWork(int id);
private:
static QMutex mutex;
static int counter;
};
#endif // MYWORKER_H
cpp
//myworker.cpp
#include "myworker.h"
// 静态成员初始化
QMutex MyWorker::mutex;
int MyWorker::counter = 0;
MyWorker::MyWorker(QObject *parent)
: QObject(parent)
{
}
void MyWorker::doWork(int id)
{
qDebug() << "Worker" << id << "started work.";
for (int i = 0; i < 100000; ++i) {
QMutexLocker locker(&mutex); // // 加锁以保护数据
++counter;
}
emit finished(); // 发送完成信号
}
int MyWorker::getCounter()
{
return counter;
}
cpp
//main.cpp
#include <QCoreApplication>
#include <QThread>
#include "myworker.h"
#include <QTimer>
#include <QMutex>
#include <QMutexLocker>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QMutex mutex; // 创建一个互斥锁
const int numThreads = 3; // 定义要启动的线程数量
QThread *threads[numThreads]; // 创建一个 QThread 指针数组,用于存储线程对象
MyWorker *workers[numThreads]; // 创建一个 MyWorker 指针数组,用于存储工作对象
int finishedCount = 0; // 初始化一个计数器,用于跟踪完成的线程数量
// 循环创建和启动线程
for (int i = 0; i < numThreads; ++i) {
threads[i] = new QThread; // 创建一个新线程
workers[i] = new MyWorker; // 创建一个新工作对象
workers[i]->moveToThread(threads[i]); // 将工作对象移动到新线程中
// 连接线程的 started 信号到工作对象的 doWork 槽,使用 QTimer 确保在事件循环开始后执行
QObject::connect(threads[i], &QThread::started, workers[i], [=]() {
QTimer::singleShot(0, workers[i], [=]() { workers[i]->doWork(i); });
});
// 连接工作对象的 finished 信号到线程的 quit 槽,以便任务完成后退出线程的事件循环
QObject::connect(workers[i], &MyWorker::finished, threads[i], &QThread::quit);
// 连接工作对象的 finished 信号到工作对象的 deleteLater 槽,以便任务完成后删除工作对象
QObject::connect(workers[i], &MyWorker::finished, workers[i], &QObject::deleteLater);
// 连接线程的 finished 信号到线程的 deleteLater 槽,以便线程退出后删除线程对象
QObject::connect(threads[i], &QThread::finished, threads[i], &QObject::deleteLater);
// 连接工作对象的 finished 信号到一个 lambda 表达式,用于更新计数器并检查所有线程是否完成
QObject::connect(workers[i], &MyWorker::finished, [&finishedCount, &mutex, i]() {
QMutexLocker locker(&mutex); // 加锁以保护计数器的访问
++finishedCount; // 增加已完成线程的计数
qDebug() << "Worker" << i << "finished. " ;
if (finishedCount == numThreads) { // 检查是否所有线程都已完成
// 每个线程使counter增加100000,正确结果应该是:numThreads*100000
qDebug() << "counter:" << MyWorker::getCounter();
}
});
threads[i]->start(); // 启动线程
}
return app.exec();
}
结果:
Worker 1 started work.
Worker 0 started work.
Worker 2 started work.
Worker 1 finished.
Worker 2 finished.
Worker 0 finished.
counter: 300000
互斥锁在代码中保护了main.cpp
中的 ++finishedCount;
以及mythread.cpp
中的++counter;
,避免了多个线程同时执行这些操作导致的错误。QMutexLocker
能够自动管理局部作用域内互斥锁的加锁和开锁。
另外,在这段代码中,不需要调用 thread.wait()
是因为 Qt 的信号和槽机制自动管理了线程的生命周期。通过 QThread::quit()
和 QObject::deleteLater()
,线程在任务完成后自动退出并清理资源。