Threads_Events_QObjects

http://qt-project.org/wiki/Threads_Events_QObjects

Threads, Events and QObjects

Warning: Beta Version

The article is almost done, but it needs a bit of polishing and some good examples. Any review or contribution is welcome! Discussion about this article happens in this thread[developer.qt.nokia.com] .

Introduction

Prerequisites

Think of it this way: threads are like salt, not like pasta.

You like salt, I like salt, we all like salt. But we eat more pasta.

— Larry McVoy

Not being a general-purpose introduction to (threads) programming, we expect you to have some previous knowledge about:

  • C++ basics (though most suggestions do apply to other languages as well);
  • Qt basics: QObjects, signals and slots, event handling;
  • what a thread is and what the relationships are between threads, processes and the operating system;
  • how to start and stop a thread, and wait for it to finish, under (at least) one major operating system;
  • how to use mutexes, semaphores and wait conditions to create thread-safe/reentrant functions, data structures, classes.

In this document we’ll follow the Qt naming conventions [doc.qt.nokia.com], which are:

  • Reentrant A class is reentrant if it’s safe to use its instances from more than one thread, provided that at most one thread is accessing the same instance at the same time. A function is reentrant if it’s safe to invoke it from more than one thread at the same, provided that each invocation references unique data. In other words, this means that users of that class/function must serialize all accesses to instances/shared data by means of some external locking mechanism.
  • Thread-safe A class is thread-safe if it’s safe to use its instances from more than one thread at the same time. A function is thread-safe if it’s safe to invoke it from more than one thread at the same time even if the invocations reference shared data.

Events and the event loop

Being an event-driven toolkit, events and event delivery play a central role in Qt architecture. In this article we’ll not give a comprehensive coverage about this topic; we’ll instead focus on some thread-related key concepts (see here [doc.qt.nokia.com] and here [doc.qt.nokia.com] for more information about the Qt event system).

作为一个event-driven tookit,事件循环是核心。上面有几个qt事件系统的链接。

An event in Qt is an object which represents something interesting that happened; the main difference between an event and a signal is that events are targeted to a specific object in our application (which decides what to do with that event), while signals are emitted “in the wild”. From a code point of view, all events are instances of some subclass of QEvent [doc.qt.nokia.com], and all QObject-derived classes can override the QObject::event() virtual method in order to handle events targeted to their instances.

Events can be generated from both inside and outside the application; for instance:

  • QKeyEvent and QMouseEvent objects represent some kind of keyboard and mouse interaction, and they come from the window manager;
  • QTimerEvent objects are sent to a QObject when one of its timers fires, and they (usually) come from the operating system;
  • QChildEvent objects are sent to a QObject when a child is added or removed, and they come from inside your Qt application.

The important thing about events is that they’re not delivered as soon as they’re generated; they’re instead queued up in an event queue and sent sometime later. The dispatcher itself loops around the event queue and sends queued events to their target objects, and therefore it is called theevent loop. Conceptually, this is how an event loop looks (see the Qt Quarterly article linked above):

事件不是一产生就派发,而是放在event queue中。dispatcher 遍历queue,并分发事件到target objects。事件循环大致如下:

  1. while (is_active)

  2. {

  3. while (!event_queue_is_empty)

  4. dispatch_next_event();

  5. wait_for_more_events();

  6. }

We enter Qt’s main event loop by running QCoreApplication::exec(); this call blocks until QCoreApplication::exit() or QCoreApplication::quit() are called, terminating the loop.

The “wait_for_more_events()” function blocks (that is, it’s not a busy wait) until some event is generated. If we think about it, all that can generate events at that point is some external source (dispatching for all internal events is now complete and there were no more pending events in the event queue to delivery). Therefore, the event loop can be woken up by:

“wait_for_more_events()”阻塞直到外部事件产生。所以,event loop可以被以下东西唤醒:

  • window manager activity (key/mouse presses, interaction with the windows, etc.);
  • sockets activity (there’s some data available to read, or a socket is writable without blocking, there’s a new incoming connection, etc.);
  • timers (i.e. a timer fired);
  • events posted from other threads (see later).

  • 窗口管理活动

  • socket活动
  • timers
  • 其他线程posted的events

In a UNIX-like system, window manager activity (i.e. X11) is notified to applications via sockets (Unix Domain or TCP/IP), since clients use them to communicate with the X server. If we decide to implement cross-thread event posting with an internal socketpair(2), all that is left is being woken up by activity on:

  • sockets;
  • timers;

which is exactly what the select(2) system call does: it watches over a set of descriptors for activity and it times out (with a configurable timeout) if there’s no activity for a certain while. All Qt needs to do is converting what select returns into an object of the right QEvent subclass and queue it up in the event queue. Now you know what’s inside an event loop :)

在unix-like的系统中,window manager activity是通过socket发送的。如果我们的跨线程event posting 通过internal socketpair(2)实现,那么事件循环只能被以下东西唤醒:

  • sockets;
  • timers;
  • 这正好是select(2)能做的:监控descriptors 的活动和可配置的timeout。Qt所要做的就是把select的返回值转化为具体的事件,并放入evnet queue。

    What requires a running event loop?

    This isn’t an exhaustive list, but if you have the overall picture, you should be able to guess which classes require a running event loop.

    • Widgets painting and interaction: QWidget::paintEvent() will be called when delivering QPaintEvent objects, which are generated both by calling QWidget::update() (i.e. internally) or by the window manager (for instance, because a hidden window was shown). The same thing holds for all kinds of interaction (keyboard, mouse, etc.): the corresponding events will require an event loop to be dispatched.
    • Timers: long story short, they’re fired when select(2) or similar calls time out, therefore you need to let Qt do those calls for you by returning to the event loop.
    • Networking: all low-level Qt networking classes (QTcpSocket, QUdpSocket, QTcpServer, etc.) are asynchronous by design. When you call read(), they just return already available data; when you call write(), they schedule the writing for later. It’s only when you return to the event loop the actual reading/writing takes place. Notice that they do offer synchronous methods (the waitFor* family of methods), but their use is discouraged because they block the event loop while waiting. High-level classes, like QNetworkAccessManager, simply do not offer any synchronous API and require an event loop.

    什么需要event loop?

    重绘

    timers:select 或者类似调用超时

    网络:qt的底层网络类都是asynchronous by design。

    Blocking the event loop

    Before discussing why you should never ever block the event loop, let’s try to figure out what this “blocking” means. Suppose you have a Button widget which emits a signal when clicked; connected to this signal there’s a slot of our Worker object, which does a lot of work. After you click the button, the stack trace will look like this (the stack grows downwards):

    1. main(int, char **)
    2. QApplication::exec()
    3. […]
    4. QWidget::event(QEvent *)
    5. Button::mousePressEvent(QMouseEvent *)
    6. Button::clicked()
    7. […]
    8. Worker::doWork()

    In main() we started the event loop, as usual, by calling QApplication::exec() (line 2). The window manager sent us the mouse click, which was picked up by the Qt kernel, converted in a QMouseEvent and sent to our widget’s event() method (line 4) by QApplication::notify() (not shown here). Since Button didn’t override event(), the base class implementation (QWidget) is called. QWidget::event() detects the event is actually a mouse click and calls the specialized event handler, that is, Button::mousePressEvent() (line 5). We overrode this method to emit the Button::clicked() signal (line 6), which invokes the Worker::doWork slot of our worker object (line 7).

    While the worker is busy working, what’s the event loop doing? You should’ve guessed it: nothing! It dispatched the mouse press event and it’s blocked waiting for the event handler to return. We managed to block the event loop, which means that no event is sent any more, until we return from the doWork() slot, up the stack, to the event loop, and let it process pending events.

    With the event delivery stuck, widgets won’t update themselves (QPaintEvent objects will sit in the queue), no further interaction with widgets is possible (for the same reason), timers won’t fire and networking communications will slow down and stop. Moreover, many window managers will detect that your application is not handling events any more and tell the user that your application isn’t responding. That’s why is so important to quickly react to events and return to the event loop as soon as possible!

    Forcing event dispatching

    So, what do we do if we have a long task to run and don’t want to block the event loop? One possible answer is to move the task into another thread: in the next sections we’ll see how to do that. We also have the option to manually force the event loop to run, by (repeatedly) calling QCoreApplication::processEvents() inside our blocking task. QCoreApplication::processEvents() will process all the events in the event queue and return to the caller.

    Another available option we can use to forcibly reenter the event loop is the QEventLoop [doc.qt.nokia.com] class. By calling QEventLoop::exec() we reenter the event loop, and we can connect signals to the QEventLoop::quit() slot to make it quit. For instance:

    可以通过强制event loop来防止阻塞。可以通过:1.在循环中调用QCoreApplication::processEvents() 。2.重入一个QEventLoop

    1. QNetworkAccessManager qnam;

    2. QNetworkReply *reply = qnam.get(QNetworkRequest(QUrl(...)));

    3. QEventLoop loop;

    4. QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));

    5. loop.exec();

    6. / reply has finished, use it /

    QNetworkReply doesn’t offer a blocking API and requires an event loop to be running. We enter a local QEventLoop, and when the reply has finished, the local event loop quits.

    Be very careful when reentering the event loop “by other paths”: it can lead to unwanted recursions! Let’s go back to the Button example. If we call QCoreApplication::processEvents() inside the doWork() slot, and the user clicks again on the button, the doWork() slot will be invoked again:

    reentering the event loop “by other paths”可能导致问题。

    1. main(int, char **)
    2. QApplication::exec()
    3. […]
    4. QWidget::event(QEvent *)
    5. Button::mousePressEvent(QMouseEvent *)
    6. Button::clicked()
    7. […]
    8. Worker::doWork() // first, inner invocation
    9. QCoreApplication::processEvents() // we manually dispatch events and…
    10. […]
    11. QWidget::event(QEvent * ) // another mouse click is sent to the Button…
    12. Button::mousePressEvent(QMouseEvent *)
    13. Button::clicked() // which emits clicked() again…
    14. […]
    15. Worker::doWork() // DANG! we’ve recursed into our slot.

    A quick and easy workaround for this is passing QEventLoop::ExcludeUserInputEvents to QCoreApplication::processEvents(), which tells the event loop to not dispatch any user input event (the events will simply stay in the queue).

    可以用参数QEventLoop::ExcludeUserInputEvents 调用 QCoreApplication::processEvents(), 防止派发user input事件。

    Luckily, the same thing does not apply to deletion events (the ones posted in the event queue by QObject::deleteLater()). In fact, they are handled in a special way by Qt, and are processed only if the running event loop has a smaller degree of “nesting” (w.r.t. event loops) than the one where deleteLater was called. For instance:

    QObject::deleteLater()也是通过deletion event实现的。Qt只在同一级别的event loop中处理它,所以不必当心局部事件循环会delete 它。

    1. QObject *object = new QObject;

    2. object->deleteLater();

    3. QDialog dialog;

    4. dialog.exec();

    will not make object a dangling pointer (the event loop entered by QDialog::exec() is more nested than the deleteLater call). The same thing applies to local event loops started with QEventLoop. The only notable exception I’ve found to this rule (as of Qt 4.7.3) is that if deleteLater is called when NO event loop is running, then the first event loop entered will pick up the event and delete the object. This is pretty much reasonable, since Qt does not know about any “outer” loop that will eventually perform the deletion, and therefore deletes the object immediately.

    .

    Qt thread classes

    A computer is a state machine. Threads are for people who can’t program state machines.

    — Alan Cox

    Qt has had thread support for many years (Qt 2.2, released on 22 Sept 2000, introduced the QThread class.), and with the 4.0 release thread support is enabled by default on all supported platforms (although it can be turned off, seehere[doc.qt.nokia.com]for more details). Qt now offers several classes for dealing with threads; let’s start with an overview.

    QThread

    QThread[doc.qt.nokia.com]is the central, low-level class for thread support in Qt. A QThread object represents one thread of execution. Due to the cross-platform nature of Qt, QThread manages to hide all the platform-specific code that is needed to use threads on different operating systems.

    In order to use a QThread to run some code in a thread, we can subclass it and override the QThread::run() method:

    1. classThread:publicQThread{
    2. protected:
    3. voidrun(){
    4. / your thread implementation goes here /
    5. }
    6. };

    Then we can use

    1. Thread*t=newThread;
    2. t->start();// start(), not run()!

    to actually start the new thread. Note that since Qt 4.4 QThread is no longer an abstract class; now the virtual method QThread::run() instead simply calls QThread::exec();, which starts thethread’s event loop(more info on this later).

    QRunnable and QThreadPool

    QRunnable[doc.qt.nokia.com]is a lightweight abstract class that can be used to start a task in another thread in a “run and forget” fashion. In order to do so, all we have to do is subclass QRunnable and implement its run() pure virtual method:

    1. classTask:publicQRunnable{
    2. public:
    3. voidrun(){
    4. / your runnable implementation goes here /
    5. }
    6. };

    To actually run a QRunnable object we use theQThreadPool[doc.qt.nokia.com]class, which manages a pool of threads. By calling QThreadPool::start(runnable) we put a QRunnable in a QThreadPool’s runqueue; as soon as a thread becomes available, the QRunnable will be picked up and run into that thread. All Qt applications have a global thread pool available by calling QThreadPool::globalInstance(), but one can always create a private QThreadPool instance and manage it explicitely.

    所有qt程序有一个全局的线程池QThreadPool::globalInstance(), 但也可以自己创建一个私有的。

    Notice that, not being a QObject, QRunnable has no built-in means of explicitely communicating something to other components; you have to code that by hand, using low-level threading primitives (like a mutex-guarded queue for collecting results, etc.).

    QtConcurrent

    QtConcurrent[doc.qt.nokia.com]is a higher-levelAPI, built on top of QThreadPool, useful to deal with the most common parallel computation patterns:map,reduce, andfilter ; it also offers a QtConcurrent::run() method that can be used to easily run a function in another thread.

    Unlike QThread and QRunnable, QtConcurrent does not require us to use low-level synchronization primitives: all QtConcurrent methods instead return aQFuture[doc.qt.nokia.com]object, which can be used to query the computation status (its progress), to pause/resume/cancel the computation, and that also contains itsresults. TheQFutureWatcher[doc.qt.nokia.com]class can be used to monitor a QFuture progress and interact with it by means of signals and slots (notice that QFuture, being a value-based class, doesn’t inherit QObject).

    Feature comparison

    \ QThread QRunnable QtConcurrent[1](http://qt-project.org/wiki/Threads_Events_QObjects#fn19164204904fe82a1e54cff)
    High levelAPI
    Job-oriented
    Builtin support for pause/resume/cancel
    Can run at a different priority
    Can run an event loop

    1Except QtConcurrent::run, which is implemented using QRunnable and therefore shares its pros and cons.

    Threads and QObjects

    Per-thread event loop

    So far we’ve always talked about “theevent loop”, taking somehow per granted that there’s only one event loop in a Qt application. This is not the case: QThread objects can start thread-local event loops running in the threads they represent. Therefore, we say that themain event loopis the one created by the thread which invoked main(), and started with QCoreApplication::exec() (whichmustbe called from that thread). This is also called theGUIthread, because it’s the only thread in whichGUI-related operations are allowed. A QThread local event loop can be started instead by calling QThread::exec() (inside its run() method):

    线程有自己的event loop。A QThread local event loop can be started instead by calling QThread::exec() (inside its run() method):

    1. classThread:publicQThread{
    2. protected:
    3. voidrun(){
    4. / ... initialize ... /
    5. exec();
    6. }
    7. };

    As we mentioned before, since Qt 4.4 QThread::run() is no longer a pure virtual method; instead, it calls QThread::exec(). Exactly like QCoreApplication, QThread has also the QThread::quit() and QThread::exit() methods to stop the event loop.

    A thread event loop delivers events for all QObjects that arelivingin that thread; this includes, by default, all objects that are created into that thread, or that were moved to that thread (more info about this later). We also say that thethread affinityof a QObject is a certain thread, meaning that the object is living in that thread. This applies to objects which are built in the constructor of a QThread object:

    一个thread的event loop 发送event给所有living在这个thread的QObject。 一个QObject 的thread affinity是object的living thread。

    1. classMyThread:publicQThread
    2. {
    3. public:
    4. MyThread()
    5. {
    6. otherObj=newQObject;
    7. }
    8. private:
    9. QObjectobj;
    10. QObject*otherObj;
    11. QScopedPointer<QObject>yetAnotherObj;
    12. };

    What’s the thread affinity of obj, otherObj, yetAnotherObj after we create a MyThread object? We must look at the thread that created them: it’s the thread that ran the MyThread constructor. Therefore, all three objects arenotliving in the MyThread thread, but in the thread that created the MyThread instance (which, by the way, is where the instance is living as well).

    We can query anytime the thread affinity of a QObject by calling QObject::thread(). Notice that QObjects created before a QCoreApplication object haveno thread affinity, and therefore no event dispatching will be done for them (in other words, QCoreApplication builds up the QThread object that represents the main thread).

    可以通过QObject::thread() 查询thread affinity of a QObject。

    Threads_Events_QObjects

    We can use the thread-safe QCoreApplication::postEvent() method for posting an event for a certain object. This will enqueue the event in the event loop of the thread the object is living in; therefore, the event will not be dispatched unless that thread has a running event loop.

    可以通过thread-safe的QCoreApplication::postEvent() 发送事件。所以,如果线程没有event loop 事件不会分发。

    It is very important to understand that QObject and all of its subclassesare not thread-safe(although they can be reentrant); therefore, you can not access a QObject from more than one thread at the same time, unless you serialize all accesses to the object’s internal data (for instance, by protecting it with a mutex). Remember that the object may be handling events dispatched by the event loop of the thread it is living in while you’re accessing it from another thread! For the same reason, you can’t delete a QObject from another thread, but you must use QObject::deleteLater(), which will post an event that will ultimately cause its deletion by the thread the object is living in.

    object可能在living 线程的event loop中处理分发的event,这时如果你从另一个线程访问object,就可能出错。所以,不能从另一个线程delete QObject ,而要使用QObject::deleteLater()。

    Moreover, QWidget and all of its subclasses, along with otherGUI-related classes (even not QObject-based, like QPixmap)are not reentranteither: they can be used exclusively from theGUIthread.

    We can change a QObject’s affinity by calling QObject::moveToThread(); this will change the affinity of the object and of its children. Since QObject is not thread-safe, we must use it from the thread the object is living in; that is, you can onlypushobjects from the thread they’re living in to other threads, and notpullthem or move them around from other threads. Moreover, Qt requires that the child of a QObject must live in the same thread where the parent is living. This implies that:

    • you can’t use QObject::moveToThread() on a object which has a parent;
    • you must not create objects in a QThread using the QThread object itself as their parent:
    1. classThread:publicQThread{
    2. voidrun(){
    3. QObject*obj=newQObject(this);// WRONG!!!
    4. }
    5. };

    This is because theQThread object is living in another thread, namely, the one in which it was created.

    Qt also requires that all objects living in a thread are deleted before the QThread object that represents the thread is destroyed; this can be easily done by creating all the objects living in that thread on the QThread::run() method’s stack.

    Signals and slots across threads

    Given these premises, how do we call methods on QObjects living in other threads? Qt offers a very nice and clean solution: we post an event in that thread’s event queue, and the handling of that event will consist in invoking the method we’re interested in (this of course requires that the thread has a running event loop). This facility is built around the method introspection provided by moc: therefore, only signals, slots and methods marked with the Q_INVOKABLE macro are invokable from other threads.

    根据这些前提,我们如何调用在另一个线程的QObjects 的方法?可以通过post event到 thread的event queue实现。这个机制是通过moc提供的方法内省来实现:所以,只有signals, slots and methods marked with the Q_INVOKABLE macro are invokable from other threads.

    The QMetaObject::invokeMethod() static method does all the work for us:

    1. QMetaObject::invokeMethod(object,"methodName",
    2. Qt::QueuedConnection,
    3. Q_ARG(type1,arg1),
    4. Q_ARG(type2,arg2));

    Notice that since the arguments need to be copied in the event which is built behind the scenes, their types need to provide a public constructor, a public destructor and a public copy constructor, and must be registered within Qt type system by using the qRegisterMetaType() function.

    可以通过QMetaObject::invokeMethod() static method 来实现(注意上面的Qt::QueuedConnection

    Signals and slots across threads work in a similar way. When we connect a signal to a slot, the fifth argument of QObject::connect is used to specify the connection type:

    • adirect connectionmeans that the slot is always invoked directly by the thread the signal is emitted from;
    • aqueued connectionmeans that an event is posted in the event queue of the thread the receiver is living in, which will be picked up by the event loop and will cause the slot invocation sometime later;
    • ablocking queued connectionis like a queued connection, but the sender thread blocks until the event is picked up by the event loop of the thread the receiver is living in, the slot is invoked, and it returns;
    • anautomatic connection(the default) means that if the thread the receiver is living in is the same as the current thread, a direct connection is used; otherwise, a queued connection is used.

    In every case, keep in mindthe thread the emitting object is living inhas no importance at all! In case of an automatic connection, Qt looks at the thread that invoked the signal and compares it with the thread the receiver is living in to determine which connection type it has to use. In particular, thecurrent Qt documentation[doc.qt.nokia.com](4.7.1)is simply wrongwhen it states:

    Auto Connection (default) The behavior is the same as the Direct Connection, if the emitter and receiver are in the same thread. The behavior is the same as the Queued Connection, if the emitter and receiver are in different threads.

    每种连接方式下,都和发信号的object的living thread 无关。自动连接的情况下,qt查看发送信号的thread,并和接收者的living thread比较

    because the emitter object’s thread affinity does not matter. For instance:

    1. classThread:publicQThread
    2. {
    3. Q_OBJECT
    4. signals:
    5. voidaSignal();
    6. protected:
    7. voidrun(){
    8. emit aSignal();
    9. }
    10. };
    11. / ... /
    12. Thread thread;
    13. Object obj;
    14. QObject::connect(&thread,SIGNAL(aSignal()),&obj,SLOT(aSlot()));
    15. thread.start();

    The signal aSignal() will be emitted by the new thread (represented by the Thread object); since it is not the thread the Object object is living in (which, by the way,is the same thread the Thread object is living in, just to stress that the sender’s thread affinity doesn’t matter), aqueued connectionwill be used.

    Another common pitfall is the following one:

    1. classThread:publicQThread
    2. {
    3. Q_OBJECT
    4. slots:
    5. voidaSlot(){
    6. / ... /
    7. }
    8. protected:
    9. voidrun(){
    10. / ... /
    11. }
    12. };
    13. / ... /
    14. Thread thread;
    15. Object obj;
    16. QObject::connect(&obj,SIGNAL(aSignal()),&thread,SLOT(aSlot()));
    17. thread.start();
    18. obj.emitSignal();

    When “obj” emits its aSignal() signal, which kind of connection will be used? You should’ve guessed it: adirect connection. That’s because the Thread object is living in the thread that emits the signal. In the aSlot() slot we could then access some Thread’s member variable while they’re being accessed by the run() method, which is running concurrently: this is the perfect recipe for disaster.

    Yet another example, probably themost importantone:

    1. classThread:publicQThread
    2. {
    3. Q_OBJECT
    4. slots:
    5. voidaSlot(){
    6. / ... /
    7. }
    8. protected:
    9. voidrun(){
    10. QObject*obj=newObject;
    11. connect(obj,SIGNAL(aSignal()),this,SLOT(aSlot()));
    12. / ... /
    13. }
    14. };

    In this case aqueued connectionis used, therefore you’re required to run an event loop in the thread the Thread object is living in.

    A solution you’ll often found in forums, blog posts etc. is to add a moveToThread(this) to the Thread constructor:

    1. classThread:publicQThread{
    2. Q_OBJECT
    3. public:
    4. Thread(){
    5. moveToThread(this);// WRONG
    6. }
    7. / ... /
    8. };

    which indeedwill work(because now the affinity of the Thread object changed), but it’s a very bad design. What’s wrong here is that we’re misunderstanding the purpose of a thread object (the QThread subclass):QThread objects are not threads; they’re control objects around a thread, therefore meant to be used from another thread (usually, the one they’re living in).

    A good way to achieve the same resultis splitting the “working” part from the “controller” part, that is, writing a QObject subclass and using QObject::moveToThread() to change its affinity:

    一个方法是分离工作的部分和控制的部分,

    1. classWorker:publicQObject
    2. {
    3. Q_OBJECT
    4. publicslots:
    5. voiddoWork(){
    6. / ... /
    7. }
    8. };
    9. / ... /
    10. QThread*thread=newQThread;
    11. Worker*worker=newWorker;
    12. connect(obj,SIGNAL(workReady()),worker,SLOT(doWork()));
    13. worker->moveToThread(thread);
    14. thread->start();

    DOs andDON’Ts

    You can…

    • … add signals to a QThread subclass. It’s perfectly safe and they’ll do the “right thing” (see above; the sender’s thread affinity does not matter).

    You shouldn’t …

    • … use moveToThread(this).
    • … force the connection type: this usually means that you’re doing something wrong, like mixing the control interface of QThread with the program logic (which should stay in a separate object which lives in that thread).
    • … add slots to a QThread subclass: they’ll be invoked from the “wrong” thread, that is, not the one the QThread object is managing, but the one that object is living in, forcing you to specify a direct connection and/or to use moveToThread(this).
    • … use QThread::terminate.

    You must not…

    • … quit your program when threads are still running. Use QThread::wait to wait for their termination.
    • … destroy a QThread while the thread that it’s managing is still running. If you want some kind of “self-destruction”, you can connect the finished() signal with the deleteLater() slot.

    When should I use threads?

    When you have to use a blockingAPI

    If you need to use a library or other code that doesn’t offer a non-blockingAPI(by means of signals and slots, or events, or callbacks, etc.), then the only viable solution in order to avoid freezing the event loop is to spawn a process or a thread. Since creating a new worker process, having it doing the job and communicating back the results is definetely harder and more expensive than just starting a thread, the latter is the most common choice.

    A good example of such anAPIisaddress resolution(just to show you that we’re not talking about 3rd-party crappyAPI. This is something included in every C library out there), which is the process of taking an host name and converting it into an address. This process involves a query to a (usually remote) system — the Domain Name System, orDNS. While, usually, the response is almost instantaneous, the remote servers might fail, some packet might get lost, the network connection might break, and so on; in short, it might take dozens of seconds before we get a reply from our query.

    The only standardAPIavailable onUNIXsystems isblocking(not only the old-fashioned gethostbyname(3), but also the newer and better getservbyname(3) and getaddrinfo(3)).QHostInfo[doc.qt.nokia.com], the Qt class that handles host name lookups, uses a QThreadPool to enable the queries to run in the background (seehere[qt.gitorious.com]; if thread support is turned off, it switches back to a blockingAPI).

    Other simple examples areimage loadingandscaling.QImageReader[doc.qt.nokia.com]andQImage[doc.qt.nokia.com]only offer blocking methods to read an image from a device, or to scale an image to a different resolution. If you’re dealing with very large images, these processes can take up to (tens of) seconds.

    When you want to scale with the number ofCPUs

    Threads allow your program to take advantage from multiprocessor systems. Since each thread is scheduled independently by the operating system, if your application is running on such a machine the scheduler is likely to run each thread on a different processorat the same time.

    For instance, consider an application that generates thumbnails from a set of images. Athread farmofnthreads (that is, a thread pool with a fixed number of threads), one per eachCPUavailable in the system (see also QThread::idealThreadCount() ), can spread the work of scaling down the images into thumbnails on all the threads, effectively gaining an almost linear speedup with the number of the processors (for simplicity’s sake, we consider theCPUbeing the bottleneck).

    When you don’t want to be possibly blocked by others

    MEH.BETTERSTARTWITHANEXAMPLE.

    This is quite an advanced topic, so feel free to skip it for now. A nice example of this use case comes from QNetworkAccessManager usage inside WebKit. WebKit is a modern browser engine, that is, a set of classes to lay out and display web pages. The Qt widget that uses WebKit is QWebView.

    QNetworkAccessManager is a Qt class that deals withHTTPrequests and responses for all purposes, we can consider it to be the networking engine of a web browser. Before Qt 4.8, it does not make use of any worker threads; all networking is handled in the same thread QNetworkAccessManager and its QNetworkReplys are living in.

    While not using threads for networking is a very good idea, it has also a major drawback: if you don’t read data from the socket as soon as possible, the kernel buffers will fill up, packets will begin to be dropped, and the transfer speed will decrease considerably.

    Socket activity (i.e., availability of some data to read from a socket) is managed by Qt’s event loop. Blocking the event loop will therefore lead to a loss of transfer performance, because nobody will be notified that there are data to read (and thus nobody will read them).

    But what could block the event loop? The sad answer is: WebKit itself! As soon as some data are received, WebKit uses them to start laying out the web page. Unfortunately, the layout process is quite complicated and expensive, therefore it blocks the event loop for a (short) while, enough to impact on ongoing transfers (broadband connections play their role here, filling up kernel buffers in a small fraction of second).

    To sum it up, what happens is something like this:

    • WebKit issues a request;
    • some data from the reply begin to arrive;
    • WebKit starts to lay out the web page using the incoming data, blocking the event loop;
    • without a running event loop, data are received by the OS, but not read from QNetworkAccessManager sockets;
    • kernel buffers will fill up, and the transfer will slow down.

    The overall page loading time is therefore worsened by this self-induced transfer slowness.

    Notice that since QNetworkAccessManagers and QNetworkReplys are QObjects, they’re not thread-safe, therefore you can’t just move them to another thread and continue using them from your thread, because they may be accessed at the same time by two threads: yours and the one they’re living in, due to events that will be dispatched to them by the latter thread’s event loop.

    As of Qt 4.8, QNetworkAccessManager now handlesHTTPrequests in a separate thread by default, so the result of unresponsiveGUIand OS buffers filling up too quickly should be cured.

    When shouldn’t I use threads?

    If you think you need threads then your processes are too fat.

    — Rob Pike

    Timers

    This is perhaps the worst form of thread abuse. If we have to invoke a method repeatedly (for instance, every second), many people end up with something like this:

    1. // VERY WRONG
    2. while(condition){
    3. doWork();
    4. sleep(1);// this is sleep(3) from the C library
    5. }

    Then they figure out that this isblocking the event loop, therefore decide to bring in threads:

    1. // WRONG
    2. classThread:publicQThread{
    3. protected:
    4. voidrun(){
    5. while(condition){
    6. // notice that "condition" may also need volatiness and mutex protection
    7. // if we modify it from other threads (!)
    8. doWork();
    9. sleep(1);// this is QThread::sleep()
    10. }
    11. }
    12. };

    A muchbetter and simpler wayof achieving the same result is simply using timers, i.e. aQTimer[doc.qt.nokia.com]object with a 1s timeout, and make the doWork() method a slot:

    1. classWorker:publicQObject
    2. {
    3. Q_OBJECT
    4. public:
    5. Worker(){
    6. connect(&timer,SIGNAL(timeout()),this,SLOT(doWork()));
    7. timer.start(1000);
    8. }
    9. privateslots:
    10. voiddoWork(){
    11. / ... /
    12. }
    13. private:
    14. QTimertimer;
    15. };

    All we need is a running event loop, then the doWork() method will be invoked each second.

    Networking / state machines

    A very common design pattern when dealing with network operations is the following one:

    1. socket->connect(host);
    2. socket->waitForConnected();
    3. data=getData();
    4. socket->write(data);
    5. socket->waitForBytesWritten();
    6. socket->waitForReadyRead();
    7. socket->read(response);
    8. reply=process(response);
    9. socket->write(reply);
    10. socket->waitForBytesWritten();
    11. / ... and so on ... /

    Needless to say, the various waitFor() calls block the caller without returning to the event loop, freezing the UI and so on. Notice that the above snippet does not take into account any error handling, otherwise it would have been even more cumbersome. What is very wrong in this design is that we’re forgetting thatnetworking is asynchronous by design*, and if we build a synchronous processing around we’re shooting ourselves in the foot. To solve this problem, many people simple move this code into a different thread.

    Another more abstract example:

    1. result=process_one_thing();
    2. if(result->something())
    3. process_this();
    4. else
    5. process_that();
    6. wait_for_user_input();
    7. input=read_user_input();
    8. process_user_input(input);
    9. / ... /

    Which has more or less the same pitfalls of the networking example.

    Let’s take a step back and consider from an higher point of view what we’re building here: we want to create astate machinethat reacts on inputs of some sort and acts consequently. For instance, with the networking example, we might want to build something like this:

    • Idle → Connecting (when calling connectToHost());
    • Connecting → Connected (when connected() is emitted);
    • Connected → LoginDataSent (when we send the login data to the server);
    • LoginDataSent → LoggedIn (the server replied with anACK)
    • LoginDataSent → LoginError (the server replied with aNACK)

    and so forth.

    Now, there are several ways to build a state machine (and Qt even offers a class for that:QStateMachine[doc.qt.nokia.com]), the simplest one being an enum (i.e. an integer) used to remember the current state. We can rewrite the above snippets like this:

    1. classObject:publicQObject
    2. {
    3. Q_OBJECT
    4. enumState{
    5. State1,State2,State3/ and so on /
    6. };
    7. State state;
    8. public:
    9. Object():state(State1)
    10. {
    11. connect(source,SIGNAL(ready()),this,SLOT(doWork()));
    12. }
    13. privateslots:
    14. voiddoWork(){
    15. switch(state){
    16. caseState1:
    17. / ... /
    18. state=State2;
    19. break;
    20. caseState2:
    21. / ... /
    22. state=State3;
    23. break;
    24. / etc. /
    25. }
    26. }
    27. };

    What the “source” object and its “ready()” signal are? Exactly what we want them to be: for instance, in the networking example, we might want to connect the socket’s QAbstractSocket::connected() and theQIODevice::readyRead() signals to our slot. Of course, we can also easily add more slots if that suits better in our case (like a slot to manage error situations, which are notified by the QAbstractSocket::error() signal). This is a true asynchronous, signal-driven design!

    Jobs splittable in chunks

    Suppose that we have a long computation which can’t be easily moved to another thread (or that it can’t be moved at all, because for instance itmust runin theGUIthread). Ifwe can split the computation in small chunks, we can return to the event loop, let it dispatch events, and make it invoke the method that processes the next chunk. This can be easily done if we remember how queued connections are implemented: an event is posted in the event loop of the thread the receiver object is living in; when the event is delivered, the corresponding slot is invoked.

    We can use QMetaObject::invokeMethod() to achieve the same result by specifying Qt::QueuedConnection as the type of the invocation; this just requires the method to be invokable, therefore it must be either a slot or marked with the Q_INVOKABLE macro. If we also want to pass parameters to the method, they need to be registered within the Qt metatype system using qRegisterMetaType(). The following snippet shows this pattern:

    1. classWorker:publicQObject
    2. {
    3. Q_OBJECT
    4. publicslots:
    5. voidstartProcessing()
    6. {
    7. processItem(0);
    8. }
    9. voidprocessItem(intindex)
    10. {
    11. / process items[index] ... /
    12. if(index<numberOfItems)
    13. QMetaObject::invokeMethod(this,
    14. "processItem",
    15. Qt::QueuedConnection,
    16. Q_ARG(int,index+1));
    17. }
    18. };

    Since there are no threads involved, it’s easy to pause/resume/cancel such a computation and collect the results back.

    Some examples

    MD5 hash

    References

    Categories:

    原文链接: https://www.cnblogs.com/cute/archive/2012/06/25/2562083.html

    欢迎关注

    微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/53492

    非原创文章文中已经注明原地址,如有侵权,联系删除

    关注公众号【高性能架构探索】,第一时间获取最新文章

    转载文章受原作者版权保护。转载请注明原作者出处!

    (0)
    上一篇 2023年2月9日 上午4:50
    下一篇 2023年2月9日 上午4:51

    相关推荐