Multi-Thread Programming Based on Qt Learning

Source: Internet
Author: User

Multi-Thread Programming Based on Qt Learning

QT provides support for threads in three forms. They are,

I. platform-independent threads
Ii. Thread-safe event delivery
3. Cross-thread signal-slot connection.

This makes it easier to develop lightweight multi-threaded Qt programs and make full use of the advantages of multi-processor machines. Multi-threaded programming is also a useful mode, which is used to solve the problem of long-time operations without the user interface losing response. In early versions of Qt, the thread support option is not selected during database construction. from 4.0, the thread is always valid.

Thread

Qt contains the following thread-related classes:

QThread provides a method to start a new thread. QThreadStorage provides thread-by-thread data storage. QMutex provides mutually exclusive locks, or the mutex QMutexLocker is a convenient class, it automatically locks and locks QMutex and locks QReadWriterLock. It provides a convenient class of QReadLocker and QWriteLocker. It automatically locks QReadWriteLock and locks QSemaphore and provides an integer semaphore, the generalized QWaitCondition of mutex provides a method to enable the thread to sleep until it is awakened by another thread. Create a thread

This article has a more humane display on this issue.

To create a thread, subclass QThread and rewrite its run () function, for example:

class MyThread : public QThread{     Q_OBJECTprotected:     void run();};void MyThread::run(){     ...}

Then, create an instance of this thread object and call QThread: start (). Therefore, the code displayed in run () will be executed in another thread.
Note: QCoreApplication: exec () must always be called in the main thread (the thread that executes main () and cannot be called from a QThread. In GUI programs, the main thread is also called a GUI thread because it is the only thread that allows the execution of GUI-related operations. In addition, you must create a QApplication (or QCoreApplication) object before creating a QThread.

Thread Synchronization

QMutex, QReadWriteLock, QSemaphore, and QWaitCondition provide thread synchronization methods. The main idea of using threads is to expect them to be executed concurrently as much as possible, and the threads must be stopped or waited at some key points. For example, if two threads attempt to access the same global variable at the same time, the result may not be as expected.
QMutex provides mutually exclusive locks or mutex volumes. At most one thread has mutex at a time. If a thread tries to access a locked mutex, it will sleep until the thread that owns mutex unlocks the mutex. Mutexes is often used to protect shared data access.
QReadWriterLock is similar to QMutex except that it treats "read" and "write" access differently. It allows multiple readers to access data in a shared manner. Using QReadWriteLock instead of QMutex can make multi-threaded programs more concurrent.

QReadWriteLock lock;void ReaderThread::run(){    // ...     lock.lockForRead();     read_file();     lock.unlock();     //...}void WriterThread::run(){   // ...     lock.lockForWrite();     write_file();     lock.unlock();    // ...}

QSemaphore is a generalization of QMutex, which can protect a certain number of identical resources. In contrast, a mutex only protects one resource. In the following example, QSemaphore is used to control access to the ring buffer, which is shared by the producer thread and consumer thread. The producer constantly writes data to the buffer until the buffer end and starts from the beginning. The consumer constantly reads data from the buffer. Semaphores have better concurrency than mutex. If we use mutex to control access to the buffer, the producer and consumer cannot access the buffer at the same time. However, we know that at the same time, there is no harm to access buffering different parts of different threads.

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 allows a thread to wake up another thread in some cases. One or more threads can block waiting for a QWaitCondition and set a condition with wakeOne () or wakeAll. WakeOne () Wake up at random, and wakeAll () Wake up all.

In the following example, the producer must first check whether the buffer is full (numUsedBytes = BufferSize). If yes, the thread stops waiting for the bufferNotFull condition. If not, add numUsedBytes to produce data in the buffer and activate the condition bufferNotEmpty. Use mutex to protect access to numUsedBytes. In addition, QWaitCondition: wait () receives a mutex as the parameter. This mutex should be initialized as locked by the calling thread. Mutex will be unlocked before the thread enters the sleep state. When a thread is awakened, mutex will be in the locked state, and the conversion from the locked state to the waiting state is an atomic operation, which prevents competition conditions. When the program starts running, only the producer can work. The consumer is blocked and waits for the bufferNotEmpty condition. Once the producer puts a byte in the buffer, the bufferNotEmpty condition is stimulated, and the consumer thread is awakened.

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;}
Reentrant and thread security

In the Qt document, the terms "reentrant" and "thread security" are used to demonstrate how a function is used for multithreaded programs. If any function of a class can be called by multiple threads at the same time on multiple different instances of this class, this class is called "reentrant. If different threads work normally on the same instance, it is called "thread security.
Most c ++ classes are inherently reentrant, because they typically reference only member data. Any thread can call such a member function on an instance of the class, as long as no other thread calls this member function on the same instance. For example, the following Counter class is reentrant:

class Counter{public:      Counter() {n=0;}      void increment() {++n;}      void decrement() {--n;}      int value() const {return n;}private:      int n;};

This class is not thread-safe, because if multiple threads attempt to modify data member n, the result is undefined. This is because the ++ and-operators in c ++ are not atomic operations. In fact, they are extended into three machine commands:

1. Load the variable value into the register.
2. Increase or decrease the value in the register.
3. Write the value in the register back to the memory.

If thread A and thread B load the old value of the variable at the same time, add value in the register and write back. Their write operations overlap, resulting in only one increase in the variable value. Obviously, access should be serialized: A should not be interrupted when performing step 123. The simplest way to make this class thread-safe is to use QMutex to protect data members:

class Counter{public:     Counter() { n = 0; }     void increment() { QMutexLocker locker(&mutex); ++n; }     void decrement() { QMutexLocker locker(&mutex); --n; }     int value() const { QMutexLocker locker(&mutex); return n; }private:     mutable QMutex mutex;     int n;};

The QMutexLocker class automatically locks the mutex In the constructor and unlocks it in the destructor. Mutex uses the mutable keyword to modify mutex, because the value () function locks and unlocks mutex, and value () is a const function.
Most Qt classes are reentrant and non-thread-safe. Some classes and functions are thread-safe. They are mainly thread-related classes, such as QMutex and QCoreApplication: postEvent ().

Thread and QObjects

QThread is inherited from QObject. It emits signals to indicate the start and end of thread execution, and also provides many slots. More interestingly, QObjects can be used for multithreading because each thread is allowed to have its own event loop.
QObject reusability
QObject is reentrant. Most of its non-GUI subclasses, such as QTimer, QTcpSocket, QUdpSocket, QHttp, QFtp, and QProcess, are reentrant. It is possible to use these classes in multiple threads at the same time. Note that these classes are designed to be created and used in a single thread. Therefore, an object is created in one thread and its function is called in another thread, such behavior cannot guarantee a good job. Note the following three constraints:
1. QObject's child should always be created in the thread created by his father. This means that you should never pass the QThread object as the parent of another object (because the QThread object itself will be created in another thread)
2. The event-driven object is only used in a single thread. Specifically, this rule applies to the "timer mechanism" and "grid module". For example, you should not start a timer or connect a socket in a thread, when this thread is not the thread where these objects are located.
3. You must ensure that all objects created in the thread are deleted before you delete the QThread. This is easy to do: you can create objects on the stack where the run () function runs.

Although QObject can be reentrant, the GUI class, especially QWidget and all its subclasses, cannot be reentrant. They are only used for the main thread. As mentioned above, QCoreApplication: exec () must also be called from that thread. In practice, GUI classes are not used in other threads. They work on the main thread and put some time-consuming operations into an independent working thread. When the working thread is completed, display the result on the screen of the main thread.

Thread-by-thread event Loop

Each thread can have its event loop. When the initial thread starts its event loop, QCoreApplication: exec () must be used. Other threads need to use QThread :: exec (). like QCoreApplication, QThreadr provides the exit (int) function, a quit () slot.

The event loop in the thread allows the thread to use non-GUI classes (such as QTimer, QTcpSocket, and QProcess) that require the event loop ). You can also connect the signals of any thread to the slots of a specific thread, that is, the signal-slot mechanism can be used across threads. For objects created before QApplication, QObject: thread () returns 0, which means that the main thread only processes shipping events for these objects and does not process other events for objects without a thread. You can use QObject: moveToThread () to change its kinship with its Children's threads. If the object has a parent, it cannot move the relationship. In another thread (instead of the thread that created it), the delete QObject object is insecure. Unless you can ensure that the object does not process the event at the same time. You can use QObject: deleteLater () to deliver a DeferredDelete event, which is finally selected by the event loop of the object thread.

If no event is cyclically run, the event is not distributed to the object. For example, if you create a QTimer object in a thread but have never called exec (), QTimer will not emit its timeout () signal. deleteLater () does not work either. (This applies to the main thread ). You can manually use the thread-safe function QCoreApplication: postEvent () to deliver an event to any object in any thread at any time, events are distributed cyclically in the thread where the object is created. The Event Filter is also supported in all threads, But it limits that the Monitored object and Monitored object are in the same thread. Similarly, QCoreApplication: sendEvent (not postEvent () is only used to deliver events to the target object in the thread that calls this function.

Access the QObject subclass from other threads

QObject and all its subclasses are non-thread-safe. This includes the entire event delivery system. It should be noted that when you are accessing an object from another thread, the event loop can deliver the event to your QObject subclass. If you call a function that does not generate a QObject subclass in the current thread, you must use mutex to protect the internal data of the QObject subclass. Otherwise, the function may experience disasters or unexpected results. Like other objects, the QThread object exists in the thread where it is created-not the thread created when the QThread: run () is called. Generally, it is insecure to provide slots in your QThread subclass unless you use mutex to protect your member variables.

On the other hand, you can safely transmit signals from the implementation of QThread: run (), because the signal transmission is thread-safe.

Cross-thread signal-Slot

Qt supports three types of signal-slot connections:

1. connect directly. slot is called immediately when signal is released. This slot is executed in the thread that emits signal (not necessarily the thread that receives the survival of the object)
2. Queue connection. When the control returns to the event loop of the thread where the object belongs, the slot is called. This slot is executed in the thread where the receiving object is alive.
3. Automatic Connection (default). If the signal is transmitted in the same thread as the receiver, its behavior is like direct connection. Otherwise, its behavior is like queue connection.

The connection type may be specified by passing parameters to connect. Note that when the sender and receiver are in different threads and the event loop is running in the receiver's thread, direct connection is not safe. In the same way, is it safe to call functions of objects in different threads. QObject: connect () itself is thread-safe.

Multithreading and implicit sharing

Qt uses the so-called implicit sharing for many of its value types to optimize performance. The principle is relatively simple. The shared class contains a pointer to the shared data block, which contains the genuine original data and a reference count. Converts a deep copy to a small copy to improve the performance. This mechanism works behind the scenes and programmers do not need to care about it. If the object needs to modify the data and the reference count is greater than 1, it should first detach (). So that its modification will not affect other sharer. Since the modified data is different from the original data, it is impossible to share it again, so it performs a deep copy first, retrieve the data and modify the data. For example:

void QPen::setStyle(Qt::PenStyle style){     detach();           // detach from common data     d->style = style;   // set the style member}void QPen::detach(){     if (d->ref != 1) {         ...             // perform a deep copy     }}

It is generally considered that implicit sharing is not in harmony with multithreading because of the existence of reference counts. One way to protect the reference count is to use mutex, but it is slow. Early versions of Qt did not provide a satisfactory solution. From 4.0, implicit shared classes can be securely copied across threads, just like other value types. They are completely reentrant. Implicit sharing is "implicit ". It uses the assembly language to implement atomic reference counting operations, which is much faster than mutex.
If you access the same object in multiple threads, you also need to use mutex to serialize the access sequence, just like other reentrant objects. In general, implicit sharing is really "hidden". In multi-threaded programs, you can regard them as general, non-shared, and reentrant types, this approach is safe.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.