Daily sentence
No matter how bitter, must remember: suffering is our life on the road indispensable experience, only alive, just have the possibility of happiness!
Posix Condition Variables for parallel programming
I saw a “Spurious wakeup” in Java Locksupport.park ().
#include <pthread.h>
struct msg {
struct msg *m_next;
/ *... more stuff here ... * /
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void) {
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock);
while (workq == NULL)
pthread_cond_wait(&qready, &qlock);
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */}}void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
Copy the code
-
A simple message producer and consumer code synchronized with condition.
-
The most confusing thing about this code is that the pthread_mutex_lock and pthread_mutex_unlock in the process_msg function are a pair of function calls that lock first and unlock later. Yes, it’s unlocked, but they’re not a pair. The other half is in the pthread_cond_wait function.
The pthread_cond_wait function can be thought of as doing three things:
-
Unlocks mutex by putting its own thread into the condition’s wait queue.
-
Wait to wake up (when another thread calls pthread_cond_signal or pthread_cond_broadcast);
-
When awakened, lock mutex and return.
-
Mutex and condition are actually bound together, and only one condition corresponds to one MUtex.
In Java code, Condition objects can only be obtained through the lock.newcondition () function.
Spurious wakeup
Spurious wakeup is when a thread calls pthread_cond_signal() and more than one thread is woken up.
Suppose there are three threads, thread A executing pthread_cond_wait, thread B executing pthread_cond_signal, and thread C preparing to execute the pthread_cond_wait function.
pthread_cond_wait(mutex, cond):
value = cond->value; / * 1 * /
pthread_mutex_unlock(mutex); / * 2 * /
pthread_mutex_lock(cond->mutex); / * 10 * /
if (value == cond->value) { / * * /
me->next_cond = cond->waiter;
cond->waiter = me;
pthread_mutex_unlock(cond->mutex);
unable_to_run(me);
} else
pthread_mutex_unlock(cond->mutex); / * * / 12
pthread_mutex_lock(mutex); / * * / 13
pthread_cond_signal(cond):
pthread_mutex_lock(cond->mutex); / * * / 3
cond->value++; / * * / 4
if (cond->waiter) { / * * / 5
sleeper = cond->waiter; / * * / 6
cond->waiter = sleeper->next_cond; / * * / 7
able_to_run(sleeper); /* 8 */
}
pthread_mutex_unlock(cond->mutex); /* 9 */
Copy the code
- Thread A performs steps 1 and 2, at which point it releases the mutex, thread B takes the mutext, and pthread_cond_signal is executed and returned.
So thread B is a so-called spurious wakeup.
/ build/buildd/eglibc – 2.19 / NPTL pthread_cond_wait. C/build/buildd/eglibc – 2.19 / NPTL pthread_cond_signal. C
Wait morphing optimization
Thus, there is a “wait morphing” optimization, where if a thread is woken up but cannot fetch a MUtex, it morphing into the wait queue for the MUtex.
The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().
Pthread_mutex_unlock and then pthread_cond_signal.
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready);
}
Copy the code
Pthread_cond_signal and then pthread_mutex_unlock:
void enqueue_msg(struct msg *mp) {
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_cond_signal(&qready);
pthread_mutex_unlock(&qlock);
}
Copy the code
-
Unlock and signal first, which has the advantage that the thread calling enqueue_MSG can compete with mutex again, which means that multiple messages can be placed in a row, which may increase efficiency. An unfair pattern similar to ReentrantLock in Java.
-
Signal first, then unlock. It is possible that a thread awakened by signal will sleep again because it cannot get the MUtex immediately (it has not been released yet), thus affecting efficiency.
While signal is invoked, mutex can be retained without the help of “predictable scheduling”. It is possible that real-time systems have such strict requirements.
Why use a while loop to determine if the condition is true?
while (workq == NULL)
pthread_cond_wait(&qready, &qlock);
Copy the code
Instead of saying if, right?
if (workq == NULL)
pthread_cond_wait(&qready, &qlock);
Copy the code
One reason is the Spurious wakeup, but even if there’s no spurious wakeup, it’s a While.
-
Thread A, thread B waits in the pthread_cond_wait function, thread C puts the message in the queue, calls pthread_cond_broadcast, and thread A gets the MUtex. After processing the message, thread A will NULL the workq.
-
Thread B gets the MUtex, so there are no resources available to thread B. So once you return from pthread_cond_wait, you still need to determine whether the condition succeeded, and if so, process it.
Pthread_cond_signal and pthread_cond_broadcast
-
Calling pthread_cond_broadcast to wake up all threads is considered a better option.
-
But I think pthread_cond_signal and pthread_cond_broadcast are two different things and cannot simply be combined in the same function call.
-
The efficiency of waking up only one wait thread is obviously not the same as that of waking up all wait threads. Condition is typically implemented using CLH or MCS. To notify all threads, the linked list must be traversed, which is obviously inefficient.
Mutex, is the condition fair?
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
volatile int mutexCount = 0;
void mutexFairTest(a){
int localCount = 0;
while(1){
pthread_mutex_lock(&lock);
__sync_fetch_and_add(&mutexCount, 1);
localCount += 1;
if(mutexCount > 100000000) {break;
}
pthread_mutex_unlock(&lock);
}
pthread_mutex_unlock(&lock);
printf("localCount:%d\n", localCount);
}
int main(a) {
pthread_mutex_lock(&lock);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_create(new pthread_t.NULL, (void* (*) (void *))&mutexFairTest, NULL);
pthread_mutex_unlock(&lock);
sleep(100);
}
Copy the code
The output is:
localCount:16930422
localCount:16525616
localCount:16850294
localCount:16129844
localCount:17329693
localCount:16234137
Copy the code
How many times/threads will pthread_cond_signal wake up? For example, if threads A and B call pthread_cond_wait and wait, and threads C and D call pthread_cond_signal at the same time, can threads A and B both wake up?
Could it be c, D, A, and then B waits and then deadlocks?
The pthread_cond_signal() function shall unblock at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond).
Therefore, the pthread_cond_signal call will wake up at least one thread in the wait if there are threads already waiting on the pthread_cond_wait call.
So it doesn’t happen that thread B above is waiting.