主页 | 所有的类 | 主要的类 | 注释的类 | 分组的类 | 函数

Qt中的线程支持

Qt对线程提供了支持,基本形式有独立于平台的线程类、线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法。

这个文档是提供给那些对多线程编程有丰富的知识和经验的听众的。推荐阅读:

警告:所有的GUI类(比如,QWidget和它的子类),操作系统核心类(比如,QProcess)和网络类都是线程安全的。

QRegExp使用一个静态缓存并且也不是线程安全的,即使通过使用QMutex来保护的QRegExp对象。

启用线程支持

在Windows上安装Qt时,在一些编译器上线程支持是一个选项。

在Mac OS X和Unix上,线程支持可以当你在运行configure脚本时添加-thread选项就可以生效了。在Unix平台上,多线程程序必须用特殊的方式连接,比如使用特殊的libc,安装程序将会创建另外一个库libqt-mt并且因此线程程序必须和这个库进行连接(使用-lqt-mt)而不是标准的Qt库。

在两个平台上,你都应该定义宏QT_THREAD_SUPPORT来编译(比如,编译时使用-DQT_THREAD_SUPPORT)。在Windows上,这个通常可以在qconfig.h写一个条目来解决。

线程类

最重要的类是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库互斥量提供了从线程而不是事件线程中调用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相关的。使用容器类,字符串或者输入/输出类,如果对象只被一个线程使用就不需要任何互斥量了。

告诫

当进行线程编程时,需要注意的一些事情:


Copyright © 2002 Trolltech Trademarks 译者:Cavendish
Qt 3.0.5版