在Qt多线程应用中,如果多个线程同时访问共享资源,可能会导致数据竞争(Data Race)和死锁(Deadlock)等问题。数据竞争会导致程序出现未定义行为,而死锁则会使程序陷入无限等待,无法继续执行。
为了避免这些问题,我们需要使用同步机制来协调多个线程的执行。同步机制可以保证多个线程按照预期的顺序访问共享资源,避免数据竞争和死锁等问题。
Qt多线程提供了互斥锁(Mutex)、信号量(Semaphore)、读写锁(Read-Write Lock)、QWaitCondition、QThread::wait() 等。同步机制可以确保在任何时候只有一个线程能够访问共享资源,从而避免竞争条件的发生
QMutex类提供的是线程之间的访问顺序化。QMutex的目的是保护一个对象/数据结构或者代码段在同一时间只有一个线程可以访问。基本使用方法如下:
QMutex mutex;
int var;
void function()
{
mutex.lock();
// 访问var
var * var;
mutex.unlock();
}
如果使用mutex加锁,却没有使用unlock解锁,那么就会造成死锁,其他线程永远也得不到访问变量的机会,所以为了解决这个问题,Qt引入了QMutexLocker类,二者直接可以配合使用更加方便简洁,示例如下:
QMutex mutex;
int var;
void function()
{
QMutextLocker locker(&mutex);
// 访问var
var * var;
}
QMutexLocker上锁,解锁的原理:在该局部变量被创建的时候上锁,当所在函数运行完毕后该QMutexLocker局部变量在栈中销毁掉,根据他自己的机制也就相对应的解锁了。注意,如果该局部变量在中间被打断,那么QMutexLocker上的锁就不会被解锁掉,因为该函数没有被完整的是执行完。QMutexLocker所创建的局部变量也没有被正确销毁销毁,可能就和QMutexLocker他自己本身的机制不服也就不会解锁。
注意:当一个线程获得了互斥锁时, 其他线程会在尝试获取锁的过程中被阻塞(Blocked),而不是直接退出。当一个线程获取到锁时,其他线程会一直等待,直到该线程释放锁为止。这种等待机制称为阻塞等待(Blocking Wait)。所以尽量减少对共享资源的访问次数,以提高程序的效率。
QReadWriteLock 是Qt中提供的一种特殊的互斥锁,它可以同时允许多个线程同时读取共享资源,但只能允许一个线程写入共享资源。这种机制的目的是提高多线程程序的效率,特别是在读操作远多于写操作的情况下。 使用示例如下:
int var;
QReadWriteLock lock;
void function()
{
lock.lockForRead();
int x = var;
lock.unlock();
}
void function2()
{
lock.lockForWrite();
var = 100;
lock.unlock();
}
和 QMutexLocker 一样,Qt同样提供了 QReadLocker和QWriteLocker,可自动上锁解锁
int var;
QReadWriteLock lock;
void fun()
{
QReadLocker(&lock);
int x = var;
}
void fun2()
{
QWriteLocker(&lock);
var = 1000;
}
QSemaphore可以看作是一个计数器,它维护了一个整数值,表示可用资源的数量。
当一个线程需要获取资源时,它可以通过 acquire() 函数申请资源,如果当前可用资源的数量大于0,则申请成功,QSemaphore 会将可用资源的数量减1,并返回true;否则为0,则申请失败, 则该线程会被阻塞,等待其他线程释放资源。
当一个线程释放资源时,它可以通过 release() 函数释放资源,此时QSemaphore会将可用资源的数量加1,并唤醒一个等待的线程。
QSemaphore是QMutex的一般化,它可以保护一定数量的相同资源,而QMutex只能保护一个资源。信号量比互斥量具有更好的并发性,我们可以利用信号量实现生产者-消费者模式,如下所示:
const int dataSize = 100000;
const int bufferSize = 1024;
char buffer[bufferSize];
QSemaphore freeBytes(bufferSize); // 可用空间的数量,初始值为1024
QSemaphore usedBtes(0); // 已使用空间的数量,初始值为0
// 生产者线程
void Producer::run()
{
for (int i = 0; i < dataSize; ++i)
{
freeBytes.acquire(); //freeBytes 减1
buffer[i % bufferSize] = i;
usedBytes.release(); //usedBytes 加1, 并唤醒等待中的的线程(消费者线程)。
}
}
// 消费者线程
void Consumer::run()
{
for (int i = 0; i < dataSize; ++i)
{
usedBytes.acquire(); //usedBytes 减1
qDebug() << buffer[i % bufferSize];
freeBytes.release(); //freeBytes 加1,并唤醒等待中的的线程(生产者线程)。
}
}
同步线程不是通过强制互斥,而是通过提供一个条件变量。其他原语使线程等待资源被解锁,而QWaitCondition使线程等待特定条件被满足。为了让等待的线程继续,可以调用wakeOne()随机唤醒一个线程,或者调用wakeAll()同时唤醒所有线程。
#include
#include
#include
class Thread : public QThread
{
public:
Thread(QWaitCondition *condition, QMutex *mutex, int *number)
: m_condition(condition), m_mutex(mutex), m_number(number)
{
}
void run() override
{
// 等待条件满足
m_mutex->lock();
while (*m_number < 5) {
m_condition->wait(m_mutex);
}
m_mutex->unlock();
qDebug() << "Thread finished";
}
private:
QWaitCondition *m_condition;
QMutex *m_mutex;
int *m_number;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QWaitCondition condition;
QMutex mutex;
int number = 0;
Thread thread(&condition, &mutex, &number);
thread.start();
// 逐渐增加number的值,直到等于5,然后通知线程条件满足
for (int i = 0; i < 5; ++i) {
mutex.lock();
++number;
if (number == 5) {
condition.wakeOne();
}
mutex.unlock();
}
thread.wait();
return a.exec();
}
在这个示例中,我们创建了一个Thread线程,它等待条件满足后退出。主线程逐渐增加number的值,直到等于5,然后通过调用QWaitCondition的wakeOne()函数通知等待线程条件已经满足。注意,在等待条件之前,线程必须先获取互斥锁,然后再等待条件,这样可以避免多个线程同时等待条件的情况。
需要注意的是,QWaitCondition必须与QMutex一起使用,这是因为等待线程需要在等待条件之前先获取互斥锁,否则可能会出现多个线程同时等待条件的情况。此外,QWaitCondition还提供了wakeAll()函数,可以一次性唤醒所有等待线程。在使用QWaitCondition时,应该根据具体情况选择合适的唤醒方式。
QThread::wait() 是Qt提供的一个线程同步机制,可以用于等待一个线程完成执行。调用该函数会使当前线程阻塞,直到指定的线程完成执行为止。
以下是一个使用QThread::wait()的示例:
#include
#include
class Thread : public QThread
{
public:
void run() override
{
qDebug() << "Thread started";
sleep(1);
qDebug() << "Thread finished";
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Thread thread;
thread.start();
qDebug() << "Waiting for thread to finish...";
thread.wait();
qDebug() << "Thread finished, exiting...";
return a.exec();
}
在这个示例中,我们创建了一个Thread线程,并调用start()函数启动它。然后,我们调用QThread::wait()函数等待该线程完成执行。该函数会使当前线程阻塞,直到指定的线程完成执行为止。
注意是当前线程,在上述例子中是 main 中的,因此应该避免在主线程中调用该函数,以免阻塞用户界面。如果需要等待线程完成执行,可以考虑使用信号槽或其他线程同步机制。