QThread线程同步
在多线程编程中,需要保证线程之间的同步问题,避免运行出现不可预期结果。以下是 QThread 线程同步的几种常见方法:
-
互斥锁:使用 QMutex 或 QReadWriteLock 等互斥量,对访问共享资源的代码进行加锁。
-
条件变量:使用 QWaitCondition 对象实现等待唤醒机制,使线程根据某个条件休眠,并在满足条件时唤醒线程。
-
信号槽机制:使用 QObject::connect() 函数建立信号槽连接,对于主线程和子线程之间的通信可使用 Qt 的信号槽机制。从子线程通过发射信号通知主线程进行操作。
-
QtConcurrent 模块:使用 QtConcurrent::run() 函数将任务提交给新的线程,然后使用 QFutureWatcher 监控该任务执行状态,以便在异步操作完成时获得通知。
-
QSemaphore :使用 QSemaphore 实现线程间的信号量机制。
需要注意的是,在多线程环境下,如果应用程序访问和修改共享存储区时没有合适的方法来同步访问,可能导致数据一致性问题或者崩溃。所以在线程间共享数据时,请注意考虑线程同步问题。
互斥锁
互斥锁是一种简单的线程同步方法。QReadWriteLocker结合QReadWriteLock, QMutexLocker结合 QMutex ,是 Qt 框架下常用的互斥锁方法,用于实现多线程之间的同步。两者都可以用于保护临界区资源,使得多个线程能够安全地访问和修改共享数据。
QReadWriteLocker
QReadWriteLocker 是一种读写锁,其设计思想基于读多写少的原则。它提供了读和写两种加锁方式,通过加读锁(QReadLocker)可使得多个线程同时对共享资源进行读操作,而在某个线程需要进行写操作时,则必须首先获得写锁(QWriteLocker),以独占式的方式控制资源的访问。
QMutexLocker
相比较而言,QMutexLocker 是一种互斥锁,它适合于那些需要在特定时刻只有一个线程能够访问临界区资源的情况。在执行被锁保护的代码块之前,程序需要获取一个独占锁,完成操作后释放锁,以便其他线程也能执行操作。
二者的选择
一般来说,在需要保护一个共享资源时,如果使用频繁读取、偶尔写入的场景,应该优先选择QReadWriteLocker,这样可以提高并发度,避免由于争用过程锁导致性能损失。如果是频繁写入或者对共享资源的访问时间较长的情况,应该使用QMutexLocker。
需要注意的是,在实际开发过程中,选择锁的类型除了要根据访问情况之外,还要综合考虑线程个数、共享资源大小和复杂度等多个因素。
QMutexLocker代码示例
#ifndef COUNTERTHREAD_H
#define COUNTERTHREAD_H
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
class Counter : public QObject {
Q_OBJECT
public:
Counter() : m_counter(0) {}
void increment() {
QMutexLocker locker(&m_lock);
++m_counter;
}
int value() const {
QMutexLocker locker(&m_lock);
return m_counter;
}
private:
mutable QMutex m_lock;
int m_counter;
};
class WorkerThread : public QThread {
Q_OBJECT
public:
WorkerThread(Counter *counter) : m_counter(counter) {}
void run() override {
for (int i = 0; i < 100000; ++i) {
m_counter->increment();
}
}
private:
Counter *m_counter;
};
#endif // COUNTERTHREAD_H
在上述示例中,我们创建了一个名为 Counter 的类,该类包含一个整型成员变量和用于对其进行自增操作和获取值的成员函数 increment() 和 value()。在这些函数中,我们都使用 QMutexLocker 对 QMutex 实例进行加锁和解锁操作,确保只有一个线程可以访问 m_counter 这个共享变量。
#include <QCoreApplication>
#include <QDebug>
#include "counterthread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Counter counter;
QList<WorkerThread*> threads;
// 启动多个线程对计数器进行操作
for (int i = 0; i < 10; ++i) {
WorkerThread *thread = new WorkerThread(&counter);
threads.append(thread);
thread->start();
}
// 等待所有线程执行完成
for (auto thread: threads) {
thread->wait();
delete thread;
}
qDebug() << "counter value is" << counter.value();
return a.exec();
}
在 main() 函数中,我们创建了多个 WorkerThread 的实例,用于并行地对 Counter 进行自增操作,等待所有线程执行完成后输出计数器的最终值。
在没有使用锁的情况下,当多个线程同时访问 Counter 实例并试图修改其内部状态时,可能会发生竞争条件问题,从而导致程序崩溃或者结果异常。通过加锁来限制多个线程对相同资源的访问,确保每次只有一个线程能够成功访问目标资源,提高程序稳定性和正确性。
需要注意的是,在实际应用场景中,为了防止死锁等问题,还要谨慎分配锁的粒度,减少锁的持有时间,以便提高并发度,提升程序的整体性能。
QReadWriteLocker代码示例
#ifndef OBJECTTHREAD_H
#define OBJECTTHREAD_H
#include <QThread>
#include <QReadWriteLock>
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject() { m_value = 0; }
void incrementValue() {
QWriteLocker locker(&m_lock);
m_value += 1;
emit valueChanged(m_value);
}
signals:
void valueChanged(int newValue);
public slots:
void getValue() {
QReadLocker locker(&m_lock);
emit valueChanged(m_value);
}
private:
int m_value;
QReadWriteLock m_lock;
};
class WorkerThread : public QThread
{
Q_OBJECT
public:
WorkerThread(MyObject* object, bool incrementMode)
: m_object(object), m_incrementMode(incrementMode) {}
protected:
void run() Q_DECL_OVERRIDE {
if (m_incrementMode) {
// 加 1 操作
for (int i = 0; i < 1000000; ++i) {
m_object->incrementValue();
QThread::msleep(1);
}
}
else {
// 获取值操作
for (int i = 0; i < 1000000; ++i) {
m_object->getValue();
QThread::msleep(1);
}
}
}
private:
MyObject* m_object;
bool m_incrementMode; // True 表示加 1 操作,False 表示获取值操作
};
#endif // OBJECTTHREAD_H
在上述代码中,我们创建了一个名为 MyObject 的类,该类包含一个值成员变量和用于设置值和获取值的槽函数 incrementValue() 和 getValue()。在 incrementValue() 函数中,我们使用 QWriteLocker 进行加锁操作,以确保这个共享数据只会被一个线程访问,在循环内对值进行加 1 操作,然后发射 valueChanged 信号,在 WorkerThread::run() 函数中,我们根据参数来执行对应的 MyObject 的增量操作或获取值操作。
#include <QCoreApplication>
#include <QDebug>
#include "objectthread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyObject object;
WorkerThread incrementThread(&object, true); // 用于增加共享值,加锁保护
WorkerThread getThread(&object, false); // 用于获取共享值,加锁保护
QObject::connect(&object, &MyObject::valueChanged, [](int value) {
qDebug() << "The new value is " << value;
});
incrementThread.start();
getThread.start();
incrementThread.wait();
getThread.wait();
return a.exec();
}
在这个示例中,我们创建了一个 MyObject 对象,同时创建了两个 WorkerThread 类型的线程,一个用于进行写入数据操作(增加值),另一个用于读取对象的值。等待线程执行完成后,程序结束。需要注意的是,在实际应用场景中,如果多个线程需要并发地读写同一个共享资源,并且存在竞争条件,那么就需要手动管理锁以避免数据不一致。