Reprint ~kxcfzyk:linux C language multi-line libraries the correct usage of conditional variables in pthread

Source: Internet
Author: User
Tags posix pthread mutex semaphore

The correct usage of conditional variables in Linux c multi-line libraries Pthread

Multithreaded C language Linuxsemaphore condition variables

(This article's reader orientation is to understand the Pthread common multithreaded API and Pthread mutex, but the condition variables are completely unknown or not fully aware of the crowd.) If you don't have any ideas about these, you may need to know some basics first.

For a typical practical application of conditional variables, you can refer to a very thin Linux thread pool implementation (i)-using mutexes and condition variables, but if you are unfamiliar with the condition variables, it is best to read this article first.

The Pthread library's conditional variable mechanism has three main APIs:

    • int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex)
    • int Pthread_cond_broadcast (pthread_cond_t *cond);
    • int pthread_cond_signal (pthread_cond_t *cond);

Note: There is also a didn't see? API that is pthread_cond_timewait, the only difference with pthread_cond_wait (see below) is the ability to specify a waiting timeout, which is not discussed here.

They are used in conjunction with several other pthread APIs to handle thread synchronization issues for a particular scenario:

    1. Several threads can not continue to go down when a condition is not satisfied, so they call pthread_cond_wait to make themselves wait in this condition (hibernate);
    2. When the condition is met, another active thread calls the PTHREAD_COND_BROADCAST notification (wakeup) of all the threads just waiting on the condition, so that they continue to run down.

This situation is very general, very basic, many more specific thread synchronization problems are the expansion of this situation, such as classic consumer/producer issues, reader/writer issues and so on. The obvious "condition" is the core of this situation, so the pthread thread synchronization mechanism is called "condition variable". You can see that the conditional variable mechanism is very similar to the Java wait/notify mechanism.

The above scenario can also be implemented with a different set of POSIX-defined thread/process synchronization mechanisms (semaphore), and the semaphore mechanism is simpler to use in real-world scenarios than the conditional variable mechanism, but the semaphore performance is inferior to the condition variable in the Pthread library.

The condition variable is declared by the pthread_cond_t data type and must be initialized before it can be used:

[CPP]View Plaincopyprint?
    1. pthread_cond_t cond = Pthread_cond_initializer;
pthread_cond_t cond = Pthread_cond_initializer;

The above is statically initialized with a predefined initialization macro, or it can be dynamically initialized with a function:

[CPP]View Plaincopyprint?
    1. pthread_cond_t cond;
    2. Pthread_cond_init (&cond, NULL);
pthread_cond_t Cond;pthread_cond_init (&cond, NULL);

Note that the condition variable should be declared globally visible, because the condition variable is accessed in multiple threads (functions). The condition variable should be destroyed when it is not used at last:

[CPP]View Plaincopyprint?
    1. Pthread_cond_destroy (&cond);
Pthread_cond_destroy (&cond);

This is the normal life cycle of the condition variable, which can be called pthread_cond_wait, pthread_cond_signal, and pthread_cond_broadcast as needed, after initialization to the time before destruction.

The Pthread_cond_signal function is similar to Pthread_cond_broadcast, but the difference is that pthread_cond_signal notifies at least one of the waiting threads, Let it run down and all other waiting threads that are not notified continue to wait (hibernate). The reason why pthread_cond_signal is not strictly only to wake up a waiting thread, is because in a multiprocessor or multicore system, may not be implemented only wake up a waiting thread, even if can force to wake up only a waiting thread, also can bring a lot of performance loss, This is not appropriate for a common base thread synchronization API.

But in a real-world scenario, we usually want to wake up a waiting thread every time pthread_cond_signal is called, such as the following:

A thread is specifically responsible for receiving packets from the network, and several other threads are specifically responsible for processing the packets. When there are no packets, the processing thread all calls pthread_cond_wait into wait. When a packet arrives, the receiving thread calls Phtread_cond_signal to wake up a processing thread and the processing thread takes away the packet to be processed. When another packet arrives, the receiving thread calls Pthread_cond_signal again to wake up a thread ...

This problem is very easy for the semaphore mechanism because the Sem_post function in the semaphore only wakes up a waiting process or thread. Although the pthread_cond_signal itself does not guarantee that only one waiting thread is woken up, the POSIX standard has considered the problem when defining this set of APIs, leaving a "backdoor" where we can solve the problem with additional code in the application.

Without considering the so-called "back door," a rough-and-look-at-the-way solution is that, in addition to the conditional variables, an additional global normal count variable indicates how many waiting threads are allowed to wake up:

[CPP]View Plaincopyprint?
    1. int global_count=0;
int global_count=0;
Then when the notification thread needs to call Pthread_cond_signal to wake up another waiting thread, it should increase the count of global variables, indicating that the number of threads allowed to wake up adds another:

[CPP]View Plaincopyprint?
    1. global_count++;
    2. Pthread_cond_signal (&cond);
global_count++;p thread_cond_signal (&cond);

Pthread_cond_signal a call, those calls pthread_cond_wait wait on the cond thread may have several wake up, simply assume that all are awakened. But in fact we just want one of them to go down, the others should not go down, then the other waiting threads should call pthread_cond_wait again to continue waiting (there is obviously a loop here).

The following question is, how to decide which waiting thread to continue to go? This can be, when everyone is awakened, we all judge whether Global_count is greater than 0, that is, the current permission is not allowed to wake the thread. If a waiting thread detects that the Global_count is greater than 0, quickly remove the global_count and then go down. At this time Global_count less one, may be 0, that is not allowed to wake up the thread, several other waiting for the thread to find this condition will not go down, again call pthread_cond_wait continue to wait:

[CPP]View Plaincopyprint?
    1. while (global_count<=0) {
    2. Pthread_cond_wait (&cond, ...);
    3. }
    4. global_count--;
while (global_count<=0) {pthread_cond_wait (&cond, ...);} global_count--;
So far the problem has basically been solved, but a new problem arises: multiple threads accessing the Global_count variable at the same time can cause race conditions. The problem seems to be easy to solve, and it's OK to use mutex protection.

For the notification thread thread:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. global_count++;
    3. Pthread_cond_signal (&cond);
    4. Pthread_mutex_unlock (&mutex);
Pthread_mutex_lock (&mutex), global_count++;p thread_cond_signal (&cond);p thread_mutex_unlock (&mutex);
For waiting Threads:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. while (global_count<=0) {
    3. Pthread_cond_wait (&cond, ...);
    4. }
    5. global_count--;
    6. Pthread_mutex_unlock (&mutex);
Pthread_mutex_lock (&mutex); while (global_count<=0) {pthread_cond_wait (&cond, ...);} global_count--;p Thread_mutex_unlock (&mutex);
The new problem comes out again: waiting for the thread to call Pthread_cond_wait is still in possession of the mutex mutex, the next time the thread wants to wake up the thread will not be able to obtain mutex mutex, and then there is a deadlock. So before calling Pthread_cond_wait to put the current thread in wait, we should untie the mutex mutex, and when the thread wakes up and returns from the Pthread_cond_wait function, we should get the mutex mutex again.

For example, like this:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. while (global_count<=0) {
    3. Pthread_mutex_unlock (&mutex);
    4. Pthread_cond_wait (&cond, ...);
    5. Pthread_mutex_lock (&mutex);
    6. }
    7. global_count--;
    8. Pthread_mutex_unlock (&mutex);
Pthread_mutex_lock (&mutex), while (global_count<=0) {pthread_mutex_unlock (&mutex);p thread_cond_wait ( &cond, ...); Pthread_mutex_lock (&mutex);} global_count--;p Thread_mutex_unlock (&mutex);
This code is still problematic, the current thread before and after the Pthread_cond_wait function call has a "very short" vacuum period that does not have a mutex mutex, but for the CPU this period of vacuum is not too short.

Assuming that a waiting thread detects global_count==0, it unlocks the mutex mutex and enters the vacuum period, and is about to call pthread_cond_wait. At this point, the notification thread added a count of Global_count and then called Pthread_cond_signal. Next, the waiting thread called pthread_cond_wait is waiting, because pthread_cond_wait's call occurs after pthread_cond_signal, so pthread_cond_wait does not return. If the waiting thread in the program is on this one, the notification is lost. The problem here seems there is no way to go, but do not forget that there is a "back door" useless, that is not mentioned in front of the pthread_cond_wait of the second parameter. Take a look at the function declaration listed at the beginning of this article, and the second parameter is impressively mutex! This guess also can guess what this second parameter is for, obviously is specifically to help us unlock the mutex, and then before the pthread_cond_wait return to automatically obtain the mutex lock. To clarify here, the mutex associated with the condition variable is not used to protect the conditional variable, as some people on the web say, and the condition variable is thread-safe to implement because it has a mutex inside itself.

So the right thing to do is:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. while (global_count<=0) {
    3. Pthread_cond_wait (&cond, &mutex);
    4. }
    5. global_count--;
    6. Pthread_mutex_unlock (&mutex);
Pthread_mutex_lock (&mutex); while (global_count<=0) {pthread_cond_wait (&cond, &mutex);} global_count--;p Thread_mutex_unlock (&mutex);

Is the question completely solved? It's a pity that it's almost. Pthread_cond_wait is one of the thread revocation points (cancellation points), which means that when a thread is stuck in hibernation because of a call to Pthread_cond_wait, another thread can call Pthread_ from the thread's ID. Cancel lets this thread force a return from pthread_cond_wait and start some cleanup work, ending with an exit.

The problem is that the pthread_cond_wait return, where the red has been highlighted, the pthread_cond_wait will be automatically obtained before returning the mutex, that is, the return has occupied the mutex mutex. In this case, the direct exit of the thread causes the mutex to be occupied, the other threads cannot acquire the mutex, and the deadlock occurs again. There are two solutions to this problem, the first is to add the code that unlocks the mutex before the thread exits, which is not difficult because POSIX defines two APIs:

[CPP]View Plaincopyprint?
    1. void Pthread_cleanup_pop (int execute);
    2. void Pthread_cleanup_push (void (*routine) (void*), void *arg);
void Pthread_cleanup_pop (int execute), void Pthread_cleanup_push (void (*routine) (void*), void *arg);

Pthread_cleanup_push is used to press a function pointer into a special stack, and when the thread exits, all functions in the special stack are ejected from the top of the stack and executed (except in the case of a return exit). Phtread_cleanup_pop is used to manually eject the function pointer from the stack top of this special stack, and the Execute parameter is not 0 o'clock, and the popup function is automatically executed.

It should be noted that the POSIX standard allows these two APIs to be implemented as macros with unclosed curly braces, so these two APIs must (preferably) be used in conjunction: they have to be one after the other (push in front) and within the same nesting level of the same function.

For example, the implementation of these two APIs might be similar to this:

[CPP]View Plaincopyprint?
    1. #define PTHREAD_CLEANUP_POP (execute) XXXX {xxxx
    2. #define Pthread_cleanup_push (routine, ARG) xxxx} xxxx
#define PTHREAD_CLEANUP_POP (execute) xxxx {xxxx#define Pthread_cleanup_push (routine, ARG) xxxx} xxxx
So that's why their invocation requirements are so strange. With these two APIs, to solve just the problem, first define a cleanup callback function:

[CPP]View Plaincopyprint?
    1. void Mutex_clean (void *mutex) {
    2. Pthread_mutex_unlock ((pthread_mutex_t*) mutex);
    3. }
void Mutex_clean (void *mutex) {Pthread_mutex_unlock ((pthread_mutex_t*) mutex);}
Then in the wait line thread call those two APIs:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. Pthread_cleanup_push (Mutex_clean, &mutex);
    3. while (global_count<=0) {
    4. Pthread_cond_wait (&cond, &mutex);
    5. }
    6. global_count--;
    7. Pthread_cleanup_pop (0);
    8. Pthread_mutex_unlock (&mutex);
Pthread_mutex_lock (&mutex);p Thread_cleanup_push (Mutex_clean, &mutex); while (global_count<=0) {Pthread_ Cond_wait (&cond, &mutex);} global_count--;p thread_cleanup_pop (0);p Thread_mutex_unlock (&mutex);
Or a slightly more concise notation:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_lock (&mutex);
    2. Pthread_cleanup_push (Mutex_clean, &mutex);
    3. while (global_count<=0) {
    4. Pthread_cond_wait (&cond, &mutex);
    5. }
    6. global_count--;
    7. Pthread_cleanup_pop (1);
Pthread_mutex_lock (&mutex);p Thread_cleanup_push (Mutex_clean, &mutex); while (global_count<=0) {Pthread_ Cond_wait (&cond, &mutex);} global_count--;p Thread_cleanup_pop (1);
Another solution is more troublesome than this: setting the mutex mutex's robust property value to Pthread_mutex_robust.

For a robust mutex, when the thread holding it does not unlock and then exits, another thread calls Pthread_mutex_lock, and the function returns a eownerdead error that can be called after the thread detects this error pthread_mutex_ Consistent the robust mutex to restore consistency, immediately after that the call to Phtread_mutex_unlock is unlocked (although the lock is not currently held by this line loads). After unlocking, you can call Pthread_mutex_lock again. When using this scheme, first declare a globally visible mutex attribute variable:

[CPP]View Plaincopyprint?
    1. pthread_mutexattr_t mutexattr;
pthread_mutexattr_t mutexattr;
Then initialize and set the value of the property:

[CPP]View Plaincopyprint?
    1. Pthread_mutexattr_init (&MUTEXATTR);
    2. Pthread_mutexattr_setrobust (&MUTEXADDTR, pthread_mutex_robust);
Pthread_mutexattr_init (&mutexattr);p thread_mutexattr_setrobust (&MUTEXADDTR, PTHREAD_MUTEX_ROBUST);
With the mutex attribute, the next step is to make a modification where the mutex is initialized, and usually our initialization of the mutex is Pthread_mutex_init (&mutex, NULL), which is now changed to:

[CPP]View Plaincopyprint?
    1. Pthread_mutex_init (&mutex, &mutexattr);
Pthread_mutex_init (&mutex, &mutexattr);
Now that the preparations have been completed, we have begun to do the work. For the notification thread: [CPP]View Plaincopyprint?
    1. while (Eownerdead==pthread_mutex_lock (&mutex)) {
    2. Pthread_mutex_consistent (&mutex);
    3. Pthread_mutex_unlock (&mutex);
    4. }
    5. global_count++;
    6. Pthread_cond_signal (&cond);
    7. Pthread_mutex_unlock (&mutex);
while (Eownerdead==pthread_mutex_lock (&mutex)) {pthread_mutex_consistent (&mutex);p Thread_mutex_unlock ( &mutex);} global_count++;p thread_cond_signal (&cond);p thread_mutex_unlock (&mutex);
For waiting Threads:

[CPP]View Plaincopyprint?
    1. while (Eownerdead==pthread_mutex_lock (&mutex)) {
    2. Pthread_mutex_consistent (&mutex);
    3. Pthread_mutex_unlock (&mutex);
    4. }
    5. while (global_count<=0) {
    6. Pthread_cond_wait (&cond, &mutex);
    7. }
    8. global_count--;
    9. Pthread_mutex_unlock (&mutex);
while (Eownerdead==pthread_mutex_lock (&mutex)) {pthread_mutex_consistent (&mutex);p Thread_mutex_unlock ( &mutex);} while (global_count<=0) {pthread_cond_wait (&cond, &mutex);} global_count--;p Thread_mutex_unlock (&mutex);
Here, all the things are finally done!

Reprint ~kxcfzyk:linux C language multi-line libraries pthread the correct usage of the condition variables step by step

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.