Qt对线程提供了支持,基本形式有独立于平台的线程类、线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法。
这个文档是提供给那些对多线程编程有丰富的知识和经验的听众的。推荐阅读:
警告:所有的GUI类(比如,QWidget和它的子类),操作系统核心类(比如,QProcess)和网络类都不是线程安全的。
QRegExp使用一个静态缓存并且也不是线程安全的,即使通过使用QMutex来保护的QRegExp对象。
最重要的类是QThread,也就是说要开始一个新的线程,就是开始执行你重新实现的QThread::run()。这和Java的线程类很相似。
为了写线程程序,在两个线程同时希望访问同一个数据时,对数据进行保护是很必要的。因此这里也有一个QMutex类,一个线程可以锁定互斥量,并且在它锁定之后,其它线程就不能再锁定这个互斥量了,试图这样做的线程都会被阻塞直到互斥量被释放。例如:
class MyClass { public: void doStuff( int );
private:
QMutex mutex;
int a;
int b; }; // 这里设置a为c,b为c*2。
void MyClass::doStuff( int c ) {
mutex.lock();
a = c;
b = c * 2;
mutex.unlock(); }
这保证了同一时间只有一个线程可以进入MyClass::doStuff(),所以b将永远等于c * 2。
另外一个线程也需要在一个给定的条件下等待其它线程的唤醒,QWaitCondition类就被提供了。线程等待的条件QWaitCondition指出发生了什么事情,阻塞将一直持续到这种事情发生。当某种事情发生了,QWaitCondition可以唤醒等待这一事件的线程之一或全部。(这和POSIX线程条件变量是具有相同功能的并且它也是Unix上的一种实现。)例如:
#include <qapplication.h>
#include <qpushbutton.h>
// 全局条件变量
QWaitCondition mycond; // Worker类实现
class Worker : public QPushButton, public QThread
{ Q_OBJECT
public:
Worker(QWidget *parent = 0, const char *name = 0)
: QPushButton(parent, name)
{
setText("Start Working");
// 连接从QPushButton继承来的信号和我们的slotClicked()方法
connect(this, SIGNAL(clicked()), SLOT(slotClicked()));
// 调用从QThread继承来的start()方法……这将立即开始线程的执行
QThread::start();
}
public slots:
void slotClicked()
{ // 唤醒等待这个条件变量的一个线程
mycond.wakeOne();
}
protected:
void run()
{
// 这个方法将被新创建的线程调用……
while ( TRUE ) {
// 锁定应用程序互斥锁,并且设置窗口标题来表明我们正在等待开始工作
qApp->lock();
setCaption( "Waiting" );
qApp->unlock();
// 等待直到我们被告知可以继续
mycond.wait();
// 如果我们到了这里,我们已经被另一个线程唤醒……让我们来设置标题来表明我们正在工作
qApp->lock();
setCaption( "Working!" );
qApp->unlock();
// 这可能会占用一些时间,几秒、几分钟或者几小时等等,因为这个一个和GUI线程分开的线程,在处理事件时,GUI线程不会停下来……
do_complicated_thing();
}
}
};
// 主线程——所有的GUI事件都由这个线程处理。
int main( int argc, char **argv )
{
QApplication app( argc, argv );
// 创建一个worker……当我们这样做的时候,这个worker将在一个线程中运行
Worker firstworker( 0, "worker" );
app.setMainWidget( &worker );
worker.show();
return app.exec();
}
只要你按下按钮,这个程序就会唤醒worker线程,这个线程将会进行并且做一些工作并且然后会回来继续等待被告知做更多的工作。如果当按钮被按下时,worker线程正在工作,那么就什么也不会发生。当线程完成了工作并且再次调用QWaitCondition::wait(),然后它就会被开始。
在Qt中,一个线程总是一个事件线程——确实是这样的,线程从窗口系统中拉出事件并且把它们分发给窗口部件。静态方法QThread::postEvent从线程中传递事件,而不同于事件线程。事件线程被唤醒并且事件就像一个普通窗口系统事件那样在事件线程中被分发。例如,你可以强制一个窗口部件通过如下这样做的一个不同的线程来进行重绘:
QWidget *mywidget;
QThread::postEvent( mywidget, new QPaintEvent( QRect(0, 0, 100, 100) ) );
这(异步地)将使mywidget重绘一块100*100的正方形区域。
Qt库互斥量提供了从线程而不是事件线程中调用Qt方法的一种方法。例如:
QApplication *qApp;
QWidget *mywidget;
qApp->lock();
mywidget->setGeometry(0,0,100,100);
QPainter p; p.begin(mywidget); p.drawLine(0,0,100,100); p.end(); qApp->unlock();
在Qt中没有使用互斥量而调用一个函数通常情况下结果将是不可预知的。从另外一个线程中调用Qt的一个GUI相关函数需要使用Qt库互斥量。在这种情况下,所有可能最终访问任何图形或者窗口系统资源的都是GUI相关的。使用容器类,字符串或者输入/输出类,如果对象只被一个线程使用就不需要任何互斥量了。
当进行线程编程时,需要注意的一些事情:
Qt 高级线程类
QtConcurrent 开启线程事务
QFutureWatcher 观测线程状态
QFuture 线程启动类
QThread创建线程
为创建一个线程,子类化QThread并且重写它的run()函数,例如:
class MyThread : public QThread
{
Q_OBJECT
protected:
void run();
};
void MyThread::run()
{
...
}
之后调用start,Qt即可创建一个线程,并在线程中执行run()函数中代码,注意UI非线程安全的。
QtConcurrent创建线程
QtConcurrent 创建线程的方法比较多, 而且QtConcurrent 本身比较特殊,若系统有空闲线程时,它会调度空闲线程,无空闲线程时将会创建一个线程。
(注意:QtConcurrent 创建线程归QthreadPool管理,若超过最大线程数,将会进入队列等待),QtConcurrent创建线程的方法多种,以下举例map函数:
QImage scale(const QImage &image)
{
qDebug() < < "Scaling image in thread" << QThread::currentThread();
return image.scaled(QSize(100, 100), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
const int imageCount = 20;
// Create a list containing imageCount images.
QList images;
for (int i = 0; i < imageCount; ++i)
images.append(QImage(1600, 1200, QImage::Format_ARGB32_Premultiplied));
// Use QtConcurrentBlocking::mapped to apply the scale function to all the
// images in the list.
QList thumbnails = QtConcurrent::blockingMapped(images, scale);
return 0;
}
Qt 线程同步
QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。例如,假如两个线程试图同时访问同一个 全局变量,结果可能不如所愿。
QMutex
QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。Mutexes常用来保护共享数据访问。
QReadWriterLock
QReadWriterLock 与QMutex相似,除了它对 “read”,”write”访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
QReadWriteLock lock;
void ReaderThread::run()
{
lock.lockForRead();
read_file();
lock.unlock();
}
void WriterThread::run()
{
lock.lockForWrite();
write_file();
lock.unlock();
}
QSemaphore
QSemaphore 是QMutex的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。下面例子中,使用QSemaphore来控制对环状缓 冲的访问,此缓冲区被生产者线程和消费者线程共享。生产者不断向缓冲写入数据直到缓冲末端,再从头开始。消费者从缓冲不断读取数据。信号量比互斥量有更好 的并发性,假如我们用互斥量来控制对缓冲的访问,那么生产者,消费者不能同时访问缓冲。然而,我们知道在同一时刻,不同线程访问缓冲的不同部分并没有什么 危害。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
class Producer : public QThread
{
public:
void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i) {
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release();
}
}
class Consumer : public QThread
{
public:
void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
freeBytes.release();
}
fprintf(stderr, "\n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
QWaitCondition
QWaitCondition 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。
下面的例子中,生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),如果是,线程停下来等待 bufferNotFull条件。如果不是,在缓冲中生产数据,增加numUsedBytes,激活条件 bufferNotEmpty。使用mutex来保护对numUsedBytes的访问。另外,QWaitCondition::wait() 接收一个mutex作为参数,这个mutex应该被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒 时,mutex会处于锁定状态,而且,从锁定状态到等待状态的转换是原子操作,这阻止了竞争条件的产生。当程序开始运行时,只有生产者可以工作。消费者被 阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex;
int numUsedBytes = 0;
class Producer : public QThread
{
public:
void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == BufferSize)
bufferNotFull.wait(&mutex);
mutex.unlock();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
mutex.lock();
++numUsedBytes;
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
class Consumer : public QThread
{
public:
void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == 0)
bufferNotEmpty.wait(&mutex);
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
联系客服