I. Problem solving and scope of application
is primarily used to wait for a condition that may require another thread to satisfy this condition. The biggest difference between this and our usual pthread_mutex_lock is that the latter is generally protected by a code snippet (i.e., a key area), or a variable, but because this variable is generally accessed in a critical area, it can be considered a critical area.
but for a condition variable, it is an event that is required, and only when the event is satisfied does the subsequent operation occur, and a question arises: what should we do if we don't meet? If a simple semaphore is used, the other party may trigger the condition and then wake up a thread via unlock, but at this point there is no wait at all, and the signal may be lost. If this side has been empty, then the utilization of the CPU is very large, so the concept of conditional waiting has been triggered .
Second, the example code on the network
A little bit of code from http://download.oracle.com/docs/cd/E19455-01/806-5257/6je9h032r/index.html
void producer (buffer_t *b, char item) {Pthread_mutex_lock (&b->mutex); while (b->occupied >= bsize) pthread_cond_wait (&b->less, &b->mutex); ASSERT (B->occupied < bsize); b->buf[b->nextin++] = Item; B->nextin%= bsize; b->occupied++; /* Now:either b->occupied < bsize and B->nextin are the index of the next empty slot in the buffer, or B->occupied = = Bsize and B->nextin is the index of the next (occupied) slots that would be emptied by a Consumer (such as B->nextin = = b->nextout) */pthread_cond_signal (&b->more); Pthread_mutex_unlock (&b->mutex); }
Char consumer (buffer_t *b) { char item; Pthread_mutex_lock (&b->mutex); while (b->occupied <= 0) pthread_cond_wait (&b->more, &b->mutex); ASSERT (b->occupied > 0); item = b->buf[b->nextout++]; B->nextout%= bsize; b->occupied--; /* Now:either b->occupied > 0 and B->nextout are the index of the next occupied slot in the buffer, or B->occupied = = 0 and B->nextout is the index of the next (empty) slots that would be filled by a producer (such a s b->nextout = = b->nextin) * /pthread_cond_signal (&b->less); Pthread_mutex_unlock (&b->mutex); return (item); }
Third, the realization of glibc
1. Data structure
/* DATA structure for conditional variable handling. The structure of
The attribute type is not exposed on purpose. */
typedef Union
{
struct
{
int __lock;variable operations that protect the cond structure itself in multi-threading are not concurrency, for example for Total_seq and WAKUP_SEQ usage and increment operations。
unsigned int __futex;The way that another thread synchronizes with the thread on the condition point, that is, if it needs to be synchronized with other threads, replace the pthread_cond_wait incoming mutex with this mutex.
__extension__ unsigned long long int __total_seq;This indicates how many threads are waiting for this signal on this condition variable.。
__extension__ unsigned long long int __wakeup_seq; How many wake operations have been performed on this condition variable.
__extension__ unsigned long long int __woken_seq;The number of threads in this conditional variable that have been actually awakened。
void *__mutex;Save pthread_cond_wait Incoming mutex, you need to ensure that both pthread_cond_wait and Pthread_cond_signal pass in the same value。
unsigned int __nwaiters; Indicates how many threads are currently in use in this cond structure, and when someone is using it, Pthread_cond_destroy needs to wait for all operations to complete.
How many times the broadcast action occurred, that is, how many times broadcast
} __data;
Char __size[__sizeof_pthread_cond_t];
__extension__ long long int __align;
} pthread_cond_t;
2, the significance of lll_futex_wait
Lll_futex_wait (&cond->__data.__futex, Futex_val, pshared);
Lll_futex_wake (&cond->__data.__nwaiters, 1, pshared);
For the first wait, we need to pass in a Futex value that we use when our user state is judged, that is, the second parameter here, Futex_val, so the kernel will determine whether the address is still this value at the time the real wait hangs, if not this wait failure. However, there is no need to pass in the judgment when doing wakup, perhaps assuming that the mutex has been acquired at this time, so there will be no other threads competing.
this should be distinguished from the values of 0, 1, and 23 used by Pthread_mutex_lock, because these are the semantics defined by the C library, and the kernel does not have any special requirements or semantic judgments about them, so the user state can arbitrarily change the value of the variable.
3, the operation of Pthread_cond_wait
Int
__pthread_cond_wait (cond, mutex)
pthread_cond_t *cond;
pthread_mutex_t *mutex;
{
struct _pthread_cleanup_buffer buffer;
struct _condvar_cleanup_buffer cbuffer;
int err;
int pshared = (Cond->__data.__mutex = = (void *) ~0l)
? Lll_shared:lll_private;
/* Make sure we are along. */
Lll_lock (Cond->__data.__lock, pshared); The members of the cond structure are going to be manipulated and judged, so the structure itself is first secured against the mutex .
/* Now we can release the mutex. */
Err = __pthread_mutex_unlock_usercnt (mutex, 0); frees the user's incoming mutex, while another thread executing pthread_cond_signal can perform the possible signal judgment through pthread_mutex_lock, but we have not yet released the data operation mutex. So when the other party executes the pthread_cond_signal, it may still wait .
if (__builtin_expect (err, 0))
{
Lll_unlock (Cond->__data.__lock, pshared);
return err;
}
/* We have one new user of the Condvar. */
++cond->__data.__total_seq; increase the number of wake-up calls in the system that need to be performed .
++cond->__data.__futex; increase Futex, mainly to ensure user-state data consistency .
Cond->__data.__nwaiters + = 1 << cond_nwaiters_shift; increase the number of uses of the cond structure .
/* Remember The mutex we is using here. If there is already a
Different address store This is a bad user bug. Do not store
Anything for pshared Condvars. */
if (Cond->__data.__mutex! = (void *) ~0l)
Cond->__data.__mutex = Mutex;
/* Prepare structure passed to cancellation handler. */
Cbuffer.cond = cond;
Cbuffer.mutex = Mutex;
/* Before we block we enable cancellation. Therefore we have to
Install a cancellation handler. */
__pthread_cleanup_push (&buffer, __condvar_cleanup, &cbuffer); register a revocation point .
/* The current values of the wakeup counter. The "Woken" counter
must exceed this value. */
unsigned long long int val;
unsigned long long int seq;
val = seq = cond->__data.__wakeup_seq;
/* Remember the broadcast counter. */
Cbuffer.bc_seq = cond->__data.__broadcast_seq;
Do
{
unsigned int futex_val = cond->__data.__futex;
/* Prepare to wait. Release the Condvar Futex. */
Lll_unlock (Cond->__data.__lock, pshared); the cond operation Mutex is really released here, and we are no longer manipulating the variables .
/* Enable asynchronous cancellation. Required by the standard. */
Cbuffer.oldtype = __pthread_enable_asynccancel ();
/* Wait until woken by signal or broadcast. */
Lll_futex_wait (&cond->__data.__futex, Futex_val, pshared); waiting on the Futex variable, because we have just saved the original value of Futex, so if the above we release the Data.lock after another thread modifies the value of this variable, then the lll_futex_wait here will return the failure, So we're going to proceed to the next round of the while loop until we have the same execution, which means we're making the right decision .
/* Disable asynchronous cancellation. */ If executed here, we have been awakened by signal .
__pthread_disable_asynccancel (Cbuffer.oldtype);
/* We are going to look at shared data again, so get the lock. */
Lll_lock (Cond->__data.__lock, pshared); to access the variable, you need to obtain a mutex .
/* If A broadcast happened, we is done. */
if (cbuffer.bc_seq! = cond->__data.__broadcast_seq)
Goto Bc_out;
/* Check Whether we are eligible for wakeup. */
val = cond->__data.__wakeup_seq;
}
while (val = = Seq | | cond->__data.__woken_seq = = val when val!=seq&&cond->data.wokenup!= It is possible to wake up in Val, another time when the thread that has been awakened has been modified and the number of threads that have been awakened has been placed.
/* Another thread woken up. */
++cond->__data.__woken_seq; increase the number of threads that have been awakened in the system .
Bc_out:broadcast jump to here .
Cond->__data.__nwaiters-= 1 << cond_nwaiters_shift;
/* If Pthread_cond_destroy is called on this varaible already,
Notify the Pthread_cond_destroy caller all waiters has left
And it can be successfully destroyed. */
if (Cond->__data.__total_seq = = -1ull
&& Cond->__data.__nwaiters < (1 << cond_nwaiters_shift))
Lll_futex_wake (&cond->__data.__nwaiters, 1, pshared);
Weare doing with the Condvar. */
Lll_unlock (Cond->__data.__lock, pshared);
/* The cancellation handling is back to normal, remove the handler. */
__pthread_cleanup_pop (&buffer, 0);
/* Get the mutex before returning. */
return __pthread_mutex_cond_lock (mutex); get the mutex mutex again, maybe sleep, because our release is transparent to the upper layer, and we have freed the mutex when we enter the function, so we have to do one more time to get the pairing.
}
4, the operation of Pthread_cond_signal
Int
__pthread_cond_signal (COND)
pthread_cond_t *cond;
{
int pshared = (Cond->__data.__mutex = = (void *) ~0l)
? Lll_shared:lll_private;
/* Make sure we are alone. */
Lll_lock (Cond->__data.__lock, pshared);
/* Is there any waiters to be woken? */
if (Cond->__data.__total_seq > Cond->__data.__wakeup_seq) If the number of wake-up times is more than the number of wakes, then a wake-up operation is performed at this time.
{
/* Yes. Mark one of them as woken. */
++cond->__data.__wakeup_seq;
++cond->__data.__futex; change the value of the Futex, the specific meaning of this value is not important, just to tell the other side, this value has changed, if the other party is using the original value, then the wait operation for Futex will fail .
/* Wake one. */
if (! __builtin_expect (Lll_futex_wake_unlock (&cond->__data.__futex, 1,
1, &cond->__data.__lock,
pshared), 0))
return 0;
Lll_futex_wake (&cond->__data.__futex, 1, pshared);
}
/* We are done. */
Lll_unlock (Cond->__data.__lock, pshared);
return 0;
}
int
__pthread_cond_broadcast (cond)
pthread_cond_t *cond;
{
int pshared = (Cond->__data.__mutex = = (void *) ~0l)
? Lll_shared:lll_private;
/* Make sure we is alone. */
Lll_lock (Cond->__data.__lock, pshared);
/* Is there any waiters to be woken? */
if (Cond->__data.__total_seq > Cond->__data. __WAKEUP_SEQ) determine if there is a thread waiting to wake .
{
/* yes. Mark them all as woken. */
cond->__data.__wakeup_seq = cond->__data.__total_seq;
cond->__data.__woken_seq = cond->__data.__total_seq;
Cond->__data.__futex = (unsigned int) cond->__data.__total_seq * 2;
int futex_val = cond->__data.__futex;
/* Signal that a broadcast happened. */
< Span style= "color: #ff00ff;" > ++cond->__data.__broadcast_seq ;
/* We are done. */
Lll_unlock (Cond->__data.__lock, pshared);
/* Don't use the requeue for pshared Condvars. */
if (Cond->__data.__mutex = = (void *) ~0l)
Goto Wake_all;
/* Wake everybody. */
pthread_mutex_t *mut = (pthread_mutex_t *) cond->__data.__mutex;
/* xxx:kernel so far doesn ' t support requeue to PI futex. */
&NBSP;&NBSP;&NB sp; /* Xxx:kernel so far can only requeue to the same type of Futex,
In this case private (we don ' t Requeue for pshared condvars) . */
if (__builtin_expect (mut->__data.__kind
& (pthread_mutex_prio_inherit_np
| Pthread_mutex_pshared_bit), 0))
goto Wake_all;
/* Lll_futex_requeue returns 0 for success and Non-zero
For errors. */
if (__builtin_expect (Lll_futex_requeue (&cond->__data.__futex, 1,
Int_max, &mut->__data.__lock,
Futex_val, Lll_private), 0) transfers the Futex to the Data.lock and wakes up, and if it fails, it wakes directly without transferring .
{
/* The Requeue functionality is not available. */
Wake_all:
Lll_futex_wake (&cond->__data.__futex, Int_max, pshared); the Int_max here is to tell the kernel to wake up all the threads waiting on this variable .
}
/* That's all. */
return 0;
}
/* We are done. */
Lll_unlock (Cond->__data.__lock, pshared);
return 0;
}
On the mutex, the kernel source parsing of condition variables