Mutex, read/write lock, semaphore, and conditional variable in qthread

Source: Internet
Author: User

In gemfield's article "from pthread to qthread", we learned about the basic usage of threads, but most of the content was said to be discussed in this article, that is, thread synchronization. Gemfield has a metaphor in "from process to thread", so it is necessary to review it below: * ********************************* to summarize the following: 1. A process is like a person in a house. 2. The clone creation thread is equivalent to adding a person to the House. 3. The fork creation process is equivalent to recreating a house, then add a person to the new house. With the above analogy, we will know a lot: 1. There are a lot of resources to share between threads: such as kitchen resources, bathroom resources, water heater resources, etc.; 2. For the process, one concept is inter-process communication (it is more complicated for you to communicate with people in another house than two people in a house). 3. Because of shared memory, therefore, data can be exchanged through a global variable. 4. At the same time, there is a new concept for the thread: A. When a person uses the restroom, you have to lock it to prevent another person from accessing the bathroom. B. When one person (or several others) sleeps, the other person can follow the previously agreed method. Wake him up; c. The power supply of the water heater should be on until the number of people who want to take a bath is reduced to 0; the above concept, in the post-gemfield text, you will not feel very esoteric or boring any more. * ********************************* For the above: when a person uses the restroom, he must lock it to prevent another person from accessing the restroom. What we use in qthread is the mutex of qmutext. Mutex is mutual
Exclusion. Pthread_mutex _ * also exists in pthread, but in qthread we can see the specific implementation through the source code in the QT framework, so pthread_mutex _ * depends on your own research. Part 1: Study of qmutex 1. Background of a small piece of code: * ********************* int number = 6; void gemfield1 () {Number * = 5; number/= 4;} void gemfield2 () {Number * = 3; number/= 2 ;} * ************************* if the following code is executed in sequence, there will be the following output logic :**************************//
Gemfield1 () Number * = 5; // number is 30 Number/= 4; // number is 7 // gemfield2 () Number * = 3; // number is 21 Number/= 2; // The number is 10 **************************, but if it is in two threads (thread 1, thread 2) what about gemfield1 () and gemfield2? * *********************** // Thread 1 calls gemfield1 () Number
* = 5; // number is 30 // thread 2 calls gemfield2 (). // thread 1 is scheduled by the system, and thread 2 is scheduled to run Number * = 3; // number is 90 Number/= 2; // number is 45 // number = 4 after thread 1 ends running; // number is 11, instead of the above 10 ************************** 2. How to solve this problem? Obviously, when we want a thread (such as thread 1) to access the variable number, other threads (such as thread 2) cannot access the number unless this thread (such as thread 1) permits; this is like a person accessing the bathroom, and another person cannot access the bathroom (we call the access area of number or the restroom area a critical area). The following describes the usage of qmutex: * *************************** qmutex
Mutex; int number = 6; void gemfield1 () {mutex. lock (); Number * = 5; number/= 4; mutex. unlock ();} void gemfield2 () {mutex. lock (); Number * = 3; number/= 2; mutex. unlock ();} * *************************** after mutex lock, until unlock, only one thread accesses the number. Note: The mutex variable is a global variable like the number variable! In the use of qmutex, we focus on the following four methods and two attributes: 1. qmutex
() // Construct one mutex2, lock () // lock 3, trylock () // attempt to lock 4, unlock () // release the other two attributes of the lock are: recursion and non-recursion. If this mutex is recursive, it indicates that it can be locked multiple times by a thread, that is, the lock and unlock are nested and unlocked. If it is not recursive, it indicates that mutex can only be locked once. The usage of the four methods has been shown in the above Code. Now let's see how qmutex achieves this? 3. How does qmutex protect critical areas? Imagine our restroom problem: what mechanism does the restroom provide to prevent another user from intruding into the bathroom? Lock! Now let's start our qmutex Journey: A. First, we need to construct a qmutex object. To understand this, we must first understand the qmutex type hierarchy and members. Class
Qbasicmutex {public: inline void lock () {If (! Fasttrylock () lockinternal ();} inline void unlock () {q_assert (d_ptr.load (); // mutex must be lockedif (! D_ptr.testandsetrelease (dummylocked (), 0) unlockinternal ();} bool trylock (INT timeout = 0) {return fasttrylock ()
| Lockinternal (timeout);} PRIVATE: inline bool fasttrylock () {return d_ptr.testandsetacquire (0, dummylocked ();} bool lockinternal (INT timeout =-1 ); void unlockinternal (); qbasicatomicpointer d_ptr; static inline qmutexdata * dummylocked () {return reinterpret_cast (quintptr (1);} friend
Class qmutex; friend class qmutexdata;}; extends class qmutex: Public qbasicmutex {public: Enum recursionmode {nonrecursive, recursive}; explicit qmutex (recursionmode mode = nonrecursive );}; ----------------- class qmutexdata {public: bool recursive; qmutexdata (qmutex: recursionmode
Mode = qmutex: nonrecursive): recursive (mode = qmutex: recursive) {}; --------------- class qmutexprivate: Public qmutexdata {public: qmutexprivate (); bool wait (INT timeout =-1); void wakeup (); // conrol the lifetime of the privatesqatomicint refcount; int
ID; bool REF () {q_assert (refcount. load ()> = 0); int C; do {c = refcount. load (); If (C = 0) return false;} while (! Refcount. testandsetrelaxed (C, C + 1); q_assert (refcount. load ()> = 0); Return true;} void deref () {q_assert (refcount. load ()> = 0); If (! Refcount. deref () release (); q_assert (refcount. Load ()
> = 0);} void release (); static qmutexprivate * allocate (); qatomicint waiters; // Number of thread waitingqatomicint possiblyunlocked; // bool saying that a timed wait timed outenum {bignumber = 0 × 100000}; // must be bigger than the possible number of waiters (number
Of threads) void derefwaiters (INT value); bool wakeup; pthread_mutex_t mutex; pthread_cond_t cond;}; ------------ the class level of qmutex has been shown above, let's take a look at how to construct a qmutex object: qmutex (recursionmode mode) {d_ptr.store (mode = recursive? New qrecursivemutexprivate: 0);} The d_ptr is defined in qbasicmutex: qbasicatomicpointer
D_ptr; assign a value to the recursive member in qmutexdata Based on the qmutex parameter. The default value is 0, that is, qmutex: nonrecursive. B. How can I implement lock () by using lock? From the above Type level, we can see that this interface is implemented by the qbasicmutex class, as follows: inline void lock () {If (! Fasttrylock () lockinternal ();} That is to say, the actual action must be performed only when the return value of fasttrylock () is 0. What is fasttrylock? Inline
Bool fasttrylock () {return d_ptr.testandsetacquire (0, dummylocked ();} What is testandsetacquire? **************************************** **************************************** * ******* Prototype: bool testandsetacquire (T * expectedvalue, T * newvalue); for x86 platforms, implement template in arch \ qatomic_i386.h:
Q_inline_template bool usage: testandsetacquire (T * expectedvalue, T * newvalue) {return testandsetordered (response, newvalue);} How is testandsetordered (expectedvalue, newvalue) implemented? The root platform is related to the compiler. For gemfield, this article is the GCC compiler on Linux, so: Template q_inline_template
Bool qbasicatomicpointer: testandsetordered (T * expectedvalue, T * newvalue) {unsigned char ret; ASM volatile ("lock \ n" "cmpxchgl % 3, % 2 \ n "" sete % 1 \ n ":" = A "(newvalue)," = QM "(RET)," + M "(_ q_value ): "R" (newvalue), "0" (expectedvalue): "Memory"); return ret! = 0 ;} **************************************** **************************************** * ***** d_ptr.testandsetacquire (0,
Dummylocked (); specifies whether the current value of d_ptr is 0. If it is 0, the value of dummylocked () is assigned to d_ptr and the true value is returned; otherwise, nothing is done, and false is returned. Static inline qmutexdata * dummylocked () {return reinterpret_cast (quintptr (1);} Sorry, ladies and gentlemen, I just came back from a bath. I found that this article cannot be written. I decided to put the underlying IMPLEMENTATION OF THE CONTENT introduced in this article after the article QT atomic operations. This article briefly introduces the concepts and usage of mutex, read/write locks, conditional variables, and semaphores. Therefore, do not read the above red-colored decoration content first. The second part is the birth of qmutexlocker. qmutexlocker is equivalent to qmutex, which provides simplified mutex operations (I .e., simplified locking and unlocking ). Isn't the mutex function implemented by qmutex good? Why is another qmutexlocker? Otherwise, observe the following code: **************************************** * ************************ int
Complexfunction (INT flag) {mutex. lock (); int retval = 0; Switch (FLAG) {Case 0: Case 1: mutex. unlock (); Return morecomplexfunction (FLAG); Case 2: {int status = anotherfunction (); If (status <0) {mutex. unlock (); Return-2;} retval = status + flag;} break; default: If (flag
> 10) {mutex. unlock (); Return-1;} break;} mutex. unlock (); Return retval ;} **************************************** * ************************** the code above truly exposes the weakness of qmutex, because as long as there is mutex. lock () must have mutex. unlock (), otherwise the resources in the critical section will no longer be accessed; and the above Code cannot ensure that the qmutex object will be unlocked (the Code may leave somewhere, ). At this time, qmutexlocker is useful, because qmutexlocker must appear in the form of local variables in the function. When its scope ends, the mutex will naturally unlock. The Code is as follows: **************************************** * *************************** int
Complexfunction (INT flag) {qmutexlocker locker (& mutex); // lock int retval = 0 when defining; Switch (FLAG) {Case 0: Case 1: return morecomplexfunction (FLAG); Case 2: {int status = anotherfunction (); If (status <0) Return-2; retval = status + flag;} break; default: if (flag> 10) Return
-1; break;} return retval; // unlocked beyond the function scope }****************************** ************************************ Part 3: although the mutex function ensures the security of critical zone resources, qreadwritelock does not conform to the actual situation. For example, resources can be read concurrently! For example, if you have a book (such as civilnet book), when someone reads a page, another person (or multiple people) can also read it. However, when one person writes a note, other people cannot write the note together, and only this person has finished writing the note, so that everyone can read it together. The role of qreadwritelock is to ensure that each thread can read a resource concurrently, but to write it, it must be actually locked (therefore, qreadwritelock is suitable for a large number of concurrent reads and occasionally writes ); the Code is as follows: **************************************** * ******************** qreadwritelock
Lock; void readerthread: Run (){... Lock. lockforread (); read_file (); lock. Unlock ();...} Void writerthread: Run (){... Lock. lockforwrite (); write_file (); lock. Unlock ();...} **************************************** * ********************** Special, for the global lock: 1. As long as there is any thread lock. lockforwrite (), all subsequent locks. lockforread () will be blocked; 2. If there is any thread lock. if the lockforwrite () action is still blocked, all subsequent locks. lockforread () will fail; 3. If there is a lock in the blocked queue. lockforwrite () has lock again. lockforread (), the write priority is higher than read, And the next execution will be lock. lockforwrite (). In most cases, qreadwritelock is a direct competitor of qmutex. like qmutex, qreadwritelock also provides its simplified class to deal with complicated locking and unlocking (also through function scope). The Code is as follows: **************************************** * *********************** qreadwritelock
Lock; qbytearray readdata () {qreadlocker locker (& lock );... Return data;} void writedata (const qbytearray & Data) {qwritelocker locker (& lock );...} **************************************** ********************** Part 4: qsemaphore provides general conditions of qmutex. qmutex is a special case of qsemaphore. qmutex can only be locked once, but qsemaphore can be locked multiple times. Of course, this is because the resources to be protected by semaphores are not the same as those protected by mutex.
Is a bunch of the same resources; for example, 1. mutex protects resources like restrooms that can only be used by one person (not in public toilets ); 2. semaphores protects places with many seat resources like parking lots and restaurants. semaphores uses two basic operations, acquire () and release (). For example, for a parking lot, generally, an LED sign is used at the entrance of the parking lot to indicate the used parking space and available parking space. If you want to park the parking space, acquire (1) will be required. In this way, available () if you are driving out of the parking lot, you need to release (1), and available () will add one. Let gemfield use code to demonstrate a circular buffer and the semaphore on it (production-consumption model): const
Int datasize = 1000; // This store will supply 1000 const int buffersize = 100 to this circular table; // The annular buffet table can accommodate a maximum of 100 portions of pork char buffer [buffersize]; // buffer is the annular self-help table qsemaphore freeplace (buffersize ); // freebytes semaphore controls the area where the meat plate is not placed and the initial value is 100. Obviously, the table is empty at the beginning of the program; qsemaphore usedplace; // usedplace
The control is the position that has been used, and it is obvious that the program has not started eating at the beginning. All right, for hotel mutton waiters, class Producer: Public qthread {public: void run (); // re-implement the run virtual function}; void Producer: Run () {for (INT I = 0; I <datasize; ++ I) {freeplace. acquire (); // The blank position minus one buffer [I % buffersize] = "M"; // put meat (McDonald's) usedplace. release (); // used location plus one} the waiter (producer) needs to produce 1000 pieces of pork (
Datasize), when he wants to put a copy of the meat produced to the ring table, he must use the freeplace semaphore to get an empty place from the ring table (100 in total ). Qsemaphore: acquire () call may be blocked if the consumption progress is not followed. Finally, the waiter uses the usedplace semaphore to release a quota. An "empty location" is successfully converted to "occupied location", which the consumer is preparing to eat. For diners (consumers): class Consumer: Public qthread {public: void run (); // re-implement the run virtual function}; void
Consumer: Run () {// The consumer must have a total of 1000 slashes for (INT I = 0; I <datasize; ++ I) {usedplace. acquire (); // if no position is used (indicating no meat is put), block eat (buffer [I % buffersize]); freeplace. release (); // after the space is finished, add} leavecanting ();} in the main function, gemfield creates two threads and uses qthread: Wait () to ensure that all threads have been executed before the program exits (that is, 1000 for loops have been completed) int
Main (INT argc, char * argv []) {qcoreapplication app (argc, argv); producer; Consumer consumer; producer. start (); consumer. start (); producer. wait (); consumer. how does the wait (); Return 0;} program run? Initially, only the waiter thread can do anything; the consumer thread is blocked -- waiting for the usedplace semaphore to be released (the initial value of available () is 0 ); when the waiter puts the first piece of pork on the table, freeplace. available ()
The value is changed to buffersize-1 and usedplace. available () is 1. at this time, both threads can work: the consumer can eat the First Half of the meat, and the waiter creates the second half of the meat; on a multi-processor machine, this program may be twice faster than the mutex-based program, because the two threads can work on different buffers at the same time. Part 5: qwaitcondition, competing with qsemaphore const int datasize = 1000; // This store will supply 1000 const int buffersize to this circular table
= 100; // The annular buffet table can accommodate a maximum of 100 portions of pork char buffer [buffersize]; qwaitcondition placenotempty; // this signal is sent when meat is put up; qwaitcondition placenotfull; // when the consumer sends this signal after eating a copy of pork, qmutex mutex; int numusedplace = 0; // The number of meat locations has been placed in order to synchronize the number of waiters and consumers, we need two conditional variables and one mutex. For more information about variable interpretation, see the preceding Code annotations. Let's take a look at the waiter's producer class: **************************************** * ******* class
Producer: Public qthread {public: void run () ;}; void Producer: Run () {for (INT I = 0; I <datasize; ++ I) {mutex. lock (); If (numusedbytes = buffersize) placenotfull. wait (& mutex); mutex. unlock (); buffer [I % buffersize] = "M"; (McDonald's again) mutex. lock (); ++ numusedbytes; placenotempty. wakeall (); mutex. unlock ();}} **************************************** * ************* before the waiter puts the meat on the ring table, first, check whether the table is full. If it is full, the waiter will wait for the placenotfull condition. at the end, the waiter increases numusedbytes by 1 and sends a signal that the buffernotempty condition is true, because numusedbytes must be greater than 0. Note that qwaitcondition: Wait ()
The function uses a mutex as its parameter. This means that mutex is locked at the beginning, and then when this thread is placenotfull. wait (& mutex); when sleeping, the mutex will be unlocked, and when this thread is awakened, the mutex will be locked again. In addition, the locked state to the wait state is atomic to prevent the occurrence of race conditions. Let's take a look at the consumer class: **************************************** * ******** class Consumer: public qthread {public: void
Run () ;}; void consumer: Run () {for (INT I = 0; I <datasize; ++ I) {mutex. lock (); If (numusedbytes = 0) placenotempty. wait (& mutex); mutex. unlock (); eat (buffer [I % buffersize]); mutex. lock ();-numusedbytes; placenotfull. wakeall (); mutex. unlock ();} leavecanting ();} **************************************** * ********** the code is similar to that of the waiter. Let's take a look at the main function: **************************************** * *********** int
Main (INT argc, char * argv []) {qcoreapplication app (argc, argv); producer; Consumer consumer; producer. start (); consumer. start (); producer. wait (); consumer. wait (); Return 0 ;} **************************************** * ********** is similar to that in the letter section, at the beginning of the program, only the waiter thread can do something. The consumer is blocked (waiting for meat) until the signal "the location is not empty" is sent. On a multi-processor machine, this program may be twice faster than the mutex-based program,
Because the two threads can work on different buffers at the same time. In fact, the QT thread library contains the qthread class, qmutexlocker, qreadwritelock, qsemaphore, and qwaitcondition classes described in the previous article from pthread to qthread on gemfield, add a atomic operation (this is the content of the next article in gemfield). With this understanding, we can use the QT thread library more confidently. Note: This article belongs to the gemfield civilnet blog (http://civilnet.cn/blog) [QT park] Forum, reprinted this article, please ensure the integrity of this article, including remarks.

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.