POSIX Thread Programming Guide (3) - Thread Synchronization

zhaozj2021-02-11  246

Yangshazhou October 2001

This is a column for POSIX thread programming. The author will tell you the POSIX thread library API on the basis of clarifying the concept. This article is the third article will tell you the thread synchronization.

One. Mutout lock Although the IPC semaphore function can also be used to implement the mutex MUTEX function in POSIX THREAD, it is clear that the functionality of Semphore is too powerful, and another set of MUTEX functions specifically for thread synchronization is defined in POSIX THREAD. .

1. There are two ways to create and destroy, create mutual lock, static mode, and dynamic mode. POSIX defines a macro to static initialization PTHREAD_MUTEX_INITIALIZER mutex as follows: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; in LinuxThreads implementation, pthread_mutex_t is a structure, and a structure PTHREAD_MUTEX_INITIALIZER is constant.

Dynamic way is to use the pthread_mutex_init () function to initialize the mutex, the API is defined as follows: int pthread_mutex_init (pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr) wherein mutexattr for designating a mutex attribute (see below), is used if the default is NULL Provincial attributes.

PTHREAD_MUTEX_DESTROY () Used to log out of a mutex, the API is defined as follows: int pthread_mutex_destroy (pthread_mutex_t * mutex) Destroying a mutex ie means to release the resources it occupied, and require the lock to be currently open. Since the mutex lock does not take any resources in Linux, the pthread_mutex_destroy () in LinuxThreads is outside the check lock state (the lock state is returned to EBUSY) without other actions.

2. The attribute of the mutex property mutex is specified when the lock is created. There is only one lock type attribute in the LinuxThreads implementation. Different lock types are different when trying to lock the mutex lock that has been locked. Current (glibc2.2.3, linuxthreads0.9) has four values ​​to choose from:

Pthread_mutex_timed_np, this is the default, that is, the ordinary lock. When a thread is locked, the remaining threads will form a waiting queue and get the lock by priority after unlocking. This lock policy guarantees the fairness of resource allocation. PTHREAD_MUTEX_RECURSIVE_NP, nested lock, allows the same thread to successfully obtain multiple times, and unlock it multiple times. If it is a different thread request, you will re-compete when the thread is unlocked. PTHREAD_MUTEX_ERRORCHECK_NP, detect the error lock, return edeadlk if the same thread requests the same lock, otherwise the same is the same as the pthread_mutex_timed_np type. This ensures that there is no shortage of dead locks when multiplexing is not allowed. Pthread_mutex_adaptive_np, adapting to the lock, the simplest lock type, only waiting for unlocking back to compete.

3. Lock operation locked operation mainly includes locking pthread_mutex_lock (), unlocked pthread_mutex_unlock () and test lock pthread_mutex_trylock (), no matter which type of lock, it is impossible to be simultaneously obtained by two different threads, but must be unlocked. For ordinary locks and adaptation lock types, the unlock can be any thread in the process; and the error error must be unlocked by the lock, otherwise returns EPERM; for nested locks, documents, and implementation requirements must be by the lock Unlock, but the experimental results show that there is no such limit, this different currently has not been explained. Threads in the same process, if there is no unlocked after the lock, any other thread can no longer get the lock. 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 is similar to pthread_mutex_lock (), and the difference is to return EBUSY instead of returning when the lock has been occupied.

4. The Linux implementation of other POSIX thread lock mechanisms is not a cancellation point, so the thread of the delay cancellation type will not leave the lock by the cancel signal. It is worth noting that if the thread is canceled before the unlock is unlocked, the lock will always keep the locked state, so if there is a cancel point in the critical section, or set the asynchronous cancellation type, you must unlock in the exit callback function. .

This lock mechanism is not safe for asynchronous signals, that is, should not use mutex during signal processing, otherwise it is easy to cause deadlocks.

two. Condition variable condition variables are a mechanism for synchronization using global variables shared by threads, mainly including two actions: a thread waits for "conditional variables to establish" and hangs; another thread makes "condition establishment" (give out " Conditions are set up signals). In order to prevent competition, the use of condition variables is always combined with a mutually exclusive lock.

1. Like the mutex, you have two types of modular dynamics, static mode use pthread_cond_initializer constants, as follows: pthread_cond_t cond = pthread_cond_initializer

Dynamic mode calls the pthread_cond_init () function, the API is defined as follows: int pthread_cond_init (pthread_cond_t * Cond, pthread_condattr_t * Cond_attr)

Although the properties are defined for the condition variables in the POSIX standard, it is not implemented in LinuxThreads, so the COND_ATTR value is usually NULL and is ignored.

Dance a condition variable needs to call pthread_cond_destroy (), and only if there is no thread waiting to wait on this condition variable to log out of this condition variable, otherwise return EBUSY. Because the condition variable implemented by Linux does not assign something, the logout action includes only check whether there is a wait thread. The API is defined as follows: int pthread_cond_destroy (pthread_cond_t * COND)

2. Wait and stimulate 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) wait condition in two ways: unconditional wait pthread_cond_wait () and timing wait pthread_cond_timedwait (), Among them, when the timing waiting method is not satisfied, return Etimeout, end waiting, where Abstime appears in the same meaning as the Time () system call, 0 indicates that Greenwich Time 0:00 on January 1, 1970 0 minutes 0 seconds.

No matter which kind of waiting, it must be combined with a mutual reverse to prevent multiple threads from simultaneously request pthread_cond_wait () (or pthread_cond_timedwait (), the following) competition condition (RACE CONDition). mutex mutex lock must be ordinary (PTHREAD_MUTEX_TIMED_NP) or to accommodate lock (PTHREAD_MUTEX_ADAPTIVE_NP), and must be locked by the thread before calling pthread_cond_wait () (pthread_mutex_lock ()), while waiting in queue before the update condition, mutex remains locked, and Unlock before the thread hangs. Before conditions satisfied, the Mutex will be rescheduled to the locking action before entering pThread_cond_wait () before it is connected to PTHREAD_COND_WAIT.

There are two forms of excitation conditions, and pthread_cond_signal () activates a thread waited for the condition, and there is a plurality of waiting threads to activate one of them; pthread_cond_broadcast () activates all waiting threads.

3. Other PTHREAD_COND_WAIT () and pthread_cond_timedwait () are implemented as a cancellation point, so the thread waiting for it will re-run immediately, leaving pthread_cond_wait () after re-locking MUTEX, and then executes the cancel action. That is to say, if pthread_cond_wait () is canceled, Mutex is kept locked, so it is necessary to define exit callback functions to unlock it.

The following example is set to demonstrate the combination of mutex and conditional variables, and canceling the impact on the conditions of the condition. In the example, two threads are started and waited for the same conditional variable, and if not using exit callback functions (see the notes in the examples), TID2 will wait in pthread_mutex_lock (). Wait forever. If the callback function is used, the condition of TID2 wait and the main thread condition can be operated.

#include

#include

#include

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% 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 don't make a note 5 pthread_cancel () action, even if there is no Sleep () delay operation, Child1 and Child2 work normally. Note 3 and Note 4 The delay is made so that Child1 has time to complete the cancel action, so that Child2 can enter the request lock operation after the Child1 exits. If there is no comment 1 and the callback function definition of the comment 2, the system will hang the place where the Child2 request lock; if it does not mean nothing, 3 and notes 4 delay, Child2 can be controlled before the CHILD1 completes the cancel action, thus Successfully perform the operation of the application lock, but may hang in pthread_cond_wait () because there is also an operation of the application MUTEX. The Child1 function is given by the standard condition variable: the callback function protection, waiting for the condition before locking, pthread_cond_wait () returns to unlock.

The condition variable mechanism is not asynchronous signal security, that is, call pthread_cond_signal () or pthread_cond_broadcast () in the signal processing function is likely to cause a deadlock.

three. The main difference between the signal light signal and the mutex and conditional variables is the concept of "light". The light is bright means that the resources are available, and the light is destroyed. If the synchronous mode of the latter two is focused on the "waiting" operation, that is, the resource is not available, the signal light mechanism focuses on the lighting, that is, inform the resource available; unlocking or excitation conditions that do not wait for the thread, no waiting light Lighting operations of threads are valid and keep the lights. Of course, such operational primitives also means more overhead. The application of the signal light except that the light is light / light, the number of lights larger than 1 can be used to indicate that the number of resources is greater than 1, which may be called a multi-light.

1. Creating and loging out of the POSIX signal light standard defines two kinds of named signal lights and unnamed signal lights, but LinuXThreads implement only unnamed lights, and the famous light is always available between multi-process, and there is no big use. Different, therefore, the following is discussed only.

INT SEM_INIT (SEM_T * SEM, INT PSHARED, Unsigned Int Value) This is an API that creates a signal light, where value is the initial value of the signal light, and Pshared indicates whether it is a multi-process sharing rather than just for a process. LinuXThreads do not implement multi-process shared signal lights, so all non-0-value PShared inputs will return SEM_INIT () to -1, and set errno to enosys. The initialized signal light is characterized by the SEM variable, used for the following lights, and the lamp operation.

INT SEM_DESTROY (SEM_T * SEM) The SMS Signal Sem requires no thread waiting for the signal light, otherwise returns -1, and set Errno as ebusy. In addition, LinuxThreads' signal lanterns do not do other actions.

2. Lighting and Write the INT SEM_POST (SEM_T * SEM) Light operation plus the signal value atomically added 1, indicating an accessible resource.

INT SEM_WAIT (SEM_T * SEM) INT SEM_TRYWAIT (SEM_T * SEM) SEM_WAIT () is a wait for light, waiting for light (greater than 0), then subtract the signal lamp atom to 1, and return. Sem_Trywait () is a non-blocking version of Sem_Wait (). If the signal count is greater than 0, the atom is reduced by 1 and returns 0, otherwise it returns to -1 immediately, and errno is set to EAGAIN.

3. Gets the lamp value int SEM_GETVALUE (SEM_T * SEM, INT * SVAL) Reads the lamp count in the SEM, existed in * sval, and returns 0.

4. Other SEM_WAIT () is implemented as a cancel point, and the SEM_POST () is the only POSIX asynchronous signal secure API that can be used for asynchronous signal processing functions on the architecture of the IAC "Comparison" instruction.

four. Asynchronous signals Since LinuxThreads is a thread implemented in the nuclear level process in the nuclear, the kernel-based asynchronous signal operation is also effective for threads. At the same time, since the asynchronous signal is always actually sent to a process, the Signal of the POSIX standard cannot be implemented to reach a process, and then the process will be distributed to all threads without blocking the signal "primitive." But only one thread can be affected.

POSIX asynchronous signal is also a feature provided by a standard C library, mainly including signal set management (sIGEMPTYSET (), SIGFILLSET (), SigaddSet (), Sigdelset (), Sigismember (), etc.), Signal Processing Function Installation (SIGAction ()) Signal blocking control (SigProcmask ()), blocking signal query (SIGSUSPEND ()), etc., which can achieve process inter-process asynchronous signal functions with functions of the signal's kill (). LinuxThreads encapsulates SigAction (), and this section discusses the extended asynchronous signal functions in LinuxThreads, including pthread_sigmask (), pthread_kill (), and sigwait (). There is no doubt that all POSIX asynchronous signal functions are available for threads. INT PTHREAD_SIGMASK (int how, const sing, const signal_t ​​* newmask, sigset_t * Oldmask) Sets the signal shield code of the thread, the semantics is the same as sigprocmask (), but the CANCEL signal that does not allow the shield and the Restart signal that does not allow the response are not allowed. The shielded signal is saved in the signal queue, which can be taken out by a SIGPENDING () function.

INT PTHREAD_KILL (PTHREAD_T THREAD, INT SIGNO) Sends a SIGNO signal to the Thread number. In the implementation, you will use the kill () system call to complete the transmission using the KILL () system call after positioning the Thread thread number to the corresponding process number.

INT Sigwait (const sign_t * set, int * SIG) hangs the thread, waiting for one of the signals specified in the SET to reach, and store the arrived signal in * SIG. POSIX standard recommendations Before calling SIGWAIT () Waiting Signal, all threads in the process should block the signal to ensure that only SIGWAIT () calorificers get the signal, so for the asynchronous signal that needs to be synchronized, always Creating any thread calls pthread_sigmask () to block the processing of the signal. Moreover, during SIGWAIT (), the signal processing function attached to this signal is not called.

If you receive the Cancel signal during the waiting period, you will immediately exit, that is, SIGWAIT () is implemented as a cancellation point.

Fives. Other synchronization methods In addition to the synchronization of the above discussion, many other processes are available for LinuxThreads, such as file system-based IPC (pipeline, UNIX domain socket, etc.), message queue (sys.v or posix), System A signal light of V, etc. It is only a point to note that LinuxThreads is viewed in the nuclear as a shared storage area, shared file system properties, shared signal processing, and shared file descriptors.

转载请注明原文地址:https://www.9cbs.com/read-4603.html

New Post(0)