This is a column about POSIX threading programming. Based on the clarification of the concept, the author will give you a detailed account of the POSIX line libraries API. This article is the third article that will tell you about thread synchronization.
A. Mutual exclusion Lock
Although the IPC semaphore mechanism can also be used in POSIX thread to implement mutex mutex functionality, it is clear that Semphore is too powerful to define another set of mutex functions specifically for thread synchronization in POSIX thread.
1. Create and destroy
There are two ways to create mutexes, both statically and dynamically. POSIX defines a macro pthread_mutex_initializer to statically initialize the mutex, as follows:
pthread_mutex_t Mutex=pthread_mutex_initializer;
In the Linuxthreads implementation, pthread_mutex_t is a structure, and pthread_mutex_initializer is a structural constant. The dynamic approach is to use the Pthread_mutex_init () function to initialize the mutex, and the API is defined as follows:
int Pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Where mutexattr is used to specify the mutex attribute (see below), and if NULL, the default property is used. Pthread_mutex_destroy () is used to unregister a mutex, and the API is defined as follows:
int Pthread_mutex_destroy (pthread_mutex_t *mutex)
Destroying a mutex means freeing up the resources it occupies and requires that the lock is currently open. Because the mutex does not occupy any resources in Linux, Pthread_mutex_destroy () in Linuxthreads has no other action in addition to checking the lock state (the lock State returns EBUSY).
2. Mutex Properties
The properties of the mutex are specified when the lock is created, there is only one lock type attribute in the Linuxthreads implementation, and the different lock types behave differently when attempting to lock an already locked mutex. The current (glibc2.2.3,linuxthreads0.9) has four values to choose from:
- PTHREAD_MUTEX_TIMED_NP, this is the default value, which is the normal lock. When a line is Cheng, the remaining thread that requests the lock forms a wait queue, and the lock is acquired by priority after unlocking. This locking strategy guarantees the fairness of resource allocation.
- pthread_mutex_recursive_np, nested locks allow the same thread to be successfully acquired multiple times for the same lock, and unlocked by multiple unlock. If it is a different thread request, re-competes when the lock line threads unlocked.
- PTHREAD_MUTEX_ERRORCHECK_NP, checks the wrong lock, returns EDEADLK if the same thread requests the same lock, otherwise the same as the PTHREAD_MUTEX_TIMED_NP type action. This ensures that the simplest case of deadlocks does not occur when multiple locks are not allowed.
- PTHREAD_MUTEX_ADAPTIVE_NP, Adaptive Lock, the simplest type of lock, only waiting to be unlocked after re-competition.
3. Lock Operation
Lock operations mainly include locking pthread_mutex_lock (), Unlock pthread_mutex_unlock () and Test lock Pthread_mutex_trylock () three, regardless of the type of lock, can not be two different threads simultaneously get , and must wait for unlocking. For normal and adaptive lock types, the unlocking can be any thread in the process, and the lock must be unlocked by the lock-in to be valid, otherwise the eperm is returned; for nested locks, the document and implementation requirements must be unlocked by the lock-in, but the experimental results show that there is no such limitation, and this difference is not yet explained. In a thread in the same process, if the lock is not unlocked, no other thread can acquire the lock again.
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
Pthread_mutex_trylock () semantics are similar to Pthread_mutex_lock () in that they are returned ebusy instead of pending waits when the lock has been occupied.
4. Other
The Linux implementation of the POSIX thread lock mechanism is not a cancellation point, so a thread that delays the cancellation type does not leave the lock wait by receiving a cancellation signal. It is important to note that if a thread is canceled before it is unlocked, the lock will remain locked forever, so if a cancellation point exists in the critical section, or if an asynchronous cancellation type is set, it must be unlocked in the exit callback function. This locking mechanism is also not an asynchronous signal security, that is, should not be used in the signal processing process of mutual exclusion lock, otherwise easily cause deadlock.
two. Condition variable
A conditional variable is a mechanism for synchronizing a global variable shared between threads, which consists of two actions: one thread waits for the condition variable to be set up, and the other thread causes the condition to be set. To prevent competition, the use of condition variables is always combined with a mutex.
1. Create and Unregister
Conditional variables, like mutexes, have both static and dynamic creation, statically using the Pthread_cond_initializer constant, as follows:
pthread_cond_t Cond=pthread_cond_initializer calls the Pthread_cond_init () function dynamically, the API is defined as follows:
int Pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *cond_attr) Although the POSIX standard defines a property for a condition variable, it is not implemented in Linuxthreads , so the cond_attr value is usually null and is ignored. Unregistering a condition variable calls Pthread_cond_destroy () to unregister the condition variable only if no thread is waiting on the condition variable, otherwise it returns EBUSY. Because the condition variables implemented by Linux do not have any resources assigned to them, the logoff action includes checking for waiting threads only. The API is defined as follows:
int Pthread_cond_destroy (pthread_cond_t *cond)
2. Wait and Excite
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
There are two ways of waiting: unconditionally Waiting for pthread_cond_wait () and timing waiting for pthread_cond_timedwait (), where the timing wait method returns Etimeout, ends the wait, if the condition is not met before the given moment. Where abstime occurs in an absolute time form that is the same as when the system call is made, 0 means January 1, 1970 0:0 0 seconds GMT. Regardless of the wait mode, you must mate with a mutex to prevent multiple threads from simultaneously requesting a race condition (Race Condition) for pthread_cond_wait () (or pthread_cond_timedwait (), hereinafter). The mutex mutex must be a normal lock (PTHREAD_MUTEX_TIMED_NP) or an adaptive lock (PTHREAD_MUTEX_ADAPTIVE_NP), and the call to Pthread_cond_wait () Before the update condition waits for the queue, the mutex remains locked and unlocked before the thread hangs into the wait before it must be Cheng (Pthread_mutex_lock ()) by this line. Before the condition satisfies thereby leaving pthread_cond_wait (), the mutex will be re-locked to correspond to the lock action before entering Pthread_cond_wait (). There are two types of excitation conditions, pthread_cond_signal () activates a thread that waits for the condition, one is activated in the queued order when there are multiple waiting threads, and pthread_cond_broadcast () activates all waiting threads.
3. Other
Both pthread_cond_wait () and pthread_cond_timedwait () are implemented as cancellation points, so the threads waiting at that point will rerun immediately, leaving Pthread_cond_wait () after the mutex is re-locked, Then perform the cancel action. That is, if Pthread_cond_wait () is canceled, the mutex remains locked, so you need to define an exit callback function to unlock it. The following example focuses on the use of mutexes and conditional variables, and the effect of cancellation on conditional wait actions. In the example, two threads are started and wait for the same condition variable, and if you do not use the exit callback function (see the comment section in the example), Tid2 will wait forever at Pthread_mutex_lock (). If you use a callback function, the conditional wait on the TID2 and the conditional excitation of the main thread will work correctly.
#i nclude <stdio.h>#i nclude <pthread.h>#i nclude <unistd.h>pthread_mutex_t mutex;pthread_cond_t Cond;void * child1 (void *arg) {pthread_cleanup_push (Pthread_mutex_unlock,&mutex); /* Comment 1 */while (1) {printf ("Thread 1 Get running n"); printf ("Thread 1 Pthread_mutex_lock returns%DN", Pthread_mutex_lock (&mutex)); Pthread_cond_wait (&cond,&mutex); printf ("Thread 1 condition appliedn"); Pthread_mutex_unlock (&mutex); Sleep (5); } pthread_cleanup_pop (0); /* Comment 2 */}void *child2 (void *arg) {while (1) {sleep (3); /* Comment 3 */printf ("Thread 2 Get RUNNING.N"); printf ("Thread 1 Pthread_mutex_lock returns%d\n", Pthread_mutex_lock (&mutex)); Pthread_cond_wait (&cond,&mutex); printf ("Thread 1 condition applied\n"); Pthread_mutex_unlock (&mutex); Sleep (5); } pthread_cleanup_pop (0); /* Comment 2 */}void *child2 (void *arg) {while (1) {sleep (3); /* Comment 3 */printf ("Thread 2 Get running.\n"); printf ("Thread 2 Pthread_mutex_lock returns%d\n", Pthread_mutex_lock (&mutex)); Pthread_cond_wait (&cond,&mutex); printf ("Thread 2 condition applied\n"); Pthread_mutex_unlock (&mutex); Sleep (1); }}int Main (void) {int tid1,tid2; printf ("Hello, condition variable test\n"); Pthread_mutex_init (&mutex,null); Pthread_cond_init (&cond,null); Pthread_create (&tid1,null,child1,null); Pthread_create (&tid2,null,child2,null); Do{sleep (2); /* Comment 4 */pthread_cancel (TID1); /* Comment 5 */sleep (2); /* Comment 6 */pthread_cond_signal (&cond); }while (1);Sleep (100); Pthread_exit (0);}
If you do not do the Pthread_cancel () Action of note 5, both child1 and child2 work correctly, even without the sleep () delay operation. The delay of note 3 and note 4 allows the child1 to complete the cancellation action, allowing Child2 to enter the request lock operation after the child1 exits. If there is no callback function definition for comments 1 and 2, the system hangs where the Child2 requests the lock, and if the delay of the comment 3 and the comment 4 is not done at the same time, the child2 can be controlled before the child1 completes the cancellation action, and the operation of the request lock is executed smoothly. However, it may be suspended in pthread_cond_wait () because there is also an operation to apply for a mutex. The Child1 function gives the use of the standard conditional variable: The callback function is protected, waits for the condition to be locked, and pthread_cond_wait () is returned to unlock.
The conditional variable mechanism is not safe for asynchronous signals, that is, calling pthread_cond_signal () or Pthread_cond_broadcast () in a signal processing function is likely to cause a deadlock.
Beacon The main difference between a semaphore and a mutex and a condition variable is the concept of "light", which means that the resource is available and the light off means it is unavailable. If the latter two-way focus on the "wait" operation, that is, the resource is not available, the beacon mechanism is focused on lighting, that is, to inform the resource is available, there is no waiting for the thread to unlock or excitation conditions are meaningless, and not waiting for the light to light the lighting operation is valid, and can keep the light state. Of course, such an operation primitive also means more overhead. The application of signal lights in addition to lights/lights out of this binary lamp, can also use more than 1 lamp number, to indicate that the number of resources greater than 1, this can be called multi-lamp.
1. Create and unregister The POSIX beacon Standard defines both the famous and nameless lights, but the linuxthreads implementation is only nameless lights, while the famous lights in addition to always can be used between the process, in use with the nameless lamp is not very different, so the following is only the nameless lights discussed. int Sem_init (sem_t *sem, int pshared, unsigned int value)
This is the API that creates the semaphore, where value is the initial value of the semaphore, and pshared indicates whether it is a multi-process share and not just a process. Linuxthreads does not implement a multi-process shared semaphore, so all pshared inputs that are not 0 values will cause sem_init () to return 1, with the errno set to Enosys. The well-initialized signals are characterized by SEM variables and are used for the following illumination and lamp operation. int Sem_destroy (SEM_T * sem)
The log off of the semaphore SEM requires that no thread is waiting for the semaphore, otherwise it returns-1, and the errno is ebusy. In addition, Linuxthreads's semaphore logoff function does not perform other actions.
2. Lighting and extinguishing lights
int sem_post (SEM_T * sem) |
The lighting operation adds 1 to the beacon value, indicating an additional accessible resource.
int sem_wait (SEM_T * sem) int sem_trywait (SEM_T * sem) |
Sem_wait () waits for the light to light up, waits for the light to light (the semaphore value is greater than 0), and then the semaphore is atomically reduced by 1 and returned. Sem_trywait () is a non-blocking version of Sem_wait (), and if the semaphore count is greater than 0, the atom is reduced by 1 and returns 0, otherwise the -1,errno is immediately returned to Eagain.
3. Get lamp value
int Sem_getvalue (SEM_T * sem, int * sval)
Reads the lamp count in the SEM, is stored in the *sval, and returns 0.
4. Other Sem_wait () is implemented as a cancellation point, and Sem_post () is the only API for POSIX asynchronous signal security that can be used for asynchronous signal processing functions on architectures that support atomic "compare and swap" directives.
Asynchronous Signal Because Linuxthreads is a thread that is implemented in a kernel-based lightweight process outside of the core, asynchronous signal operations on the core are also valid for threads. But at the same time, because the asynchronous signal is always actually destined for a process, it is not possible to implement the "signal that the POSIX standard requires to reach a process, and then the process will distribute the signal to all the threads that do not block the signal", but only one of the threads is affected. The POSIX asynchronous signal is also a standard C library feature that mainly includes signal set management (Sigemptyset (), Sigfillset (), Sigaddset (), Sigdelset (), Sigismember (), etc.), Signal Processing function installation (Sigaction ()), signal blocking control (Sigprocmask ()), Blocked signal query (sigpending ()), Signal Waiting (Sigsuspend ()), and so on, they are associated with the kill () sending signal The function of inter-process asynchronous signal can be realized with equal functions. Linuxthreads encapsulates Sigaction () and raise () around a thread, this section focuses on the extended asynchronous signal functions in Linuxthreads, including Pthread_sigmask (), Pthread_kill (), and sigwait () three functions. There is no doubt that all POSIX asynchronous signal functions are available to threads. int pthread_sigmask (int how, const sigset_t *newmask, sigset_t *oldmask)
Sets the signal mask for the thread, which is the same semantics as Sigprocmask (), but protects the cancel signal that does not allow masking and the restart signal that does not allow the response. The blocked signal is stored in the signal queue and can be removed by the sigpending () function. int Pthread_kill (pthread_t thread, int signo)
Sends a signo signal to the thread line. The implementation uses the Kill () system call to complete the send after the thread number is positioned to the corresponding process number. int sigwait (const sigset_t *set, int *sig)
Suspends the thread, waits for one of the specified signals in the set to arrive, and deposits the incoming signal into the *sig. The POSIX standard recommends that before calling Sigwait () to wait for a signal, all threads in the process should mask the signal to ensure that only the caller of Sigwait () obtains the signal, so for asynchronous signals that need to wait for synchronization, it should always be called before any thread is created Pthread_ Sigmask () masks the processing of the signal. Also, the signal processing function that was attached to the signal is not called during the call to Sigwait (). If the cancel signal is received during the wait, exit the wait immediately, that is, sigwait () is implemented as a cancellation point.
Other synchronization methods In addition to the synchronization methods discussed above, many other interprocess communication tools are also available for linuxthreads, such as file-system-based IPC (pipelines, UNIX domain sockets, etc.), Message Queuing (SYS.V or POSIX), and System v beacons. Only one thing to note is that Linuxthreads is seen in the kernel as a separate process for shared storage, shared file system attributes, shared signal processing, and shared file descriptors.
POSIX Threading Programming Guide (3)