Thread synchronization mechanism in Linux

Source: Internet
Author: User
The biggest feature of threads is resource sharing, but the synchronization problem in resource sharing is the difficulty of multi-thread programming. In linux, multiple methods are provided to process thread synchronization.

The biggest feature of threads is resource sharing, but the synchronization problem in resource sharing is the difficulty of multi-thread programming. In linux, multiple methods are provided to process thread synchronization. The most common methods are mutex lock, conditional variables, traffic signals, and asynchronous signals.

I. mutex lock

Although the IPC semaphore mechanism can also be used in Posix Thread to implement mutex functions, the semphore function is obviously too powerful, the Posix Thread defines another set of mutex functions for Thread synchronization.

1. create and destroy

There are two ways to create mutex lock, static mode and dynamic mode.

POSIX defines a macro PTHREAD_MUTEX_INITIALIZER to initialize mutex statically. the method is as follows: pthread_mutex_t mutex = timeout; in LinuxThreads implementation, pthread_mutex_t is a structure, while PTHREAD_MUTEX_INITIALIZER is a structural constant.

The dynamic mode uses the pthread_mutex_init () function to initialize the mutex lock. the API definition is as follows: int pthread_mutex_init (pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr) here, mutexattr is used to specify the mutex lock attribute (see below). if it is NULL, the default attribute is used.

Pthread_mutex_destroy () is used to cancel a mutex lock. the API definition is as follows: int pthread_mutex_destroy (pthread_mutex_t * mutex) to destroy a mutex lock, which means to release the resources it occupies, the lock is currently open. In Linux, mutex locks do not occupy any resources. Therefore, pthread_mutex_destroy () in LinuxThreads has no action except checking the lock status (EBUSY is returned when the lock status is locked.

2. mutex lock attributes

The mutex lock attribute 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 a locked mutex lock. Currently (glibc2.2.3, linuxthreads0.9) has four values available:

PTHREAD_MUTEX_TIMED_NP, which is the default value, that is, the common lock. When a thread locks, other threads requesting the lock form a waiting queue and obtain the lock by priority after unlocking. This lock policy ensures the fairness of resource allocation.
PTHREAD_MUTEX_RECURSIVE_NP: nested lock, which allows the same thread to successfully obtain the same lock multiple times and unlock the lock multiple times. For different thread requests, re-compete when the lock thread is unlocked.
PTHREAD_MUTEX_ERRORCHECK_NP: check the error lock. if the same thread requests the same lock, EDEADLK is returned. Otherwise, the action is the same as that of the PTHREAD_MUTEX_TIMED_NP type. This ensures that no deadlock will occur in the simplest case when many locks are not allowed.
PTHREAD_MUTEX_ADAPTIVE_NP: The most simple lock type to adapt to the lock. it only waits for the unlock before re-competition.

3. lock operation

The lock operation mainly includes locking pthread_mutex_lock (), unlocking pthread_mutex_unlock (), and testing locking pthread_mutex_trylock (). No matter which type of lock, it cannot be obtained by two different threads at the same time, you must wait for unlocking. For common locks and adaptive locks, the locker can be any thread in the same process, and the error locks must be unlocked by the locker; otherwise, the EPERM is returned. for nested locks, the document and implementation requirements must be unlocked by the locker, but the experiment results show that there is no such restriction. this difference is not yet explained. If the lock is not unlocked for a thread in the same process, no other thread can obtain the lock again.

// Lock and unlock

int pthread_mutex_lock(pthread_mutex_t *mutex) int pthread_mutex_unlock(pthread_mutex_t *mutex)int pthread_mutex_trylock(pthread_mutex_t *mutex) 

The semantics of pthread_mutex_trylock () is similar to that of pthread_mutex_lock (). The difference is that EBUSY is returned when the lock is occupied rather than waiting.

4. Miscellaneous

The implementation of POSIX thread lock mechanism in Linux is not a cancellation point. Therefore, threads with delayed cancellation do not leave the lock wait because they receive the cancellation signal. It is worth noting that if the thread is canceled before being unlocked after the lock is applied, the lock will always remain locked. Therefore, if there is a cancellation point in the key segment, or, if the asynchronous cancellation type is set, you must unlock it in the exit callback function.

This lock mechanism is not asynchronous signal security at the same time. that is to say, mutex locks should not be used during signal processing; otherwise, deadlock may occur.

 #include 
 
  #include 
  
   #include 
   
    #include 
    
     #include "iostream"using namespace std;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int tmp;void* thread(void *arg){    cout << "thread id is " << pthread_self() << endl;    pthread_mutex_lock(&mutex);    tmp = 12;    cout << "Now a is " << tmp << endl;    pthread_mutex_unlock(&mutex);    return NULL;}int main(){    pthread_t id;    cout << "main thread id is " << pthread_self() << endl;    tmp = 3;    cout << "In main func tmp = " << tmp << endl;    if (!pthread_create(&id, NULL, thread, NULL))    {        cout << "Create thread success!" << endl;    }    else    {        cout << "Create thread failed!" << endl;    }    pthread_join(id, NULL);    pthread_mutex_destroy(&mutex);    return 0;}
    
   
  
 

II. condition variables

A condition variable is a mechanism for synchronizing global variables shared by threads. It mainly includes two actions: one thread waits for the condition variable to be established and suspends; another thread sets "condition to true" (a signal indicating condition to true ). To prevent competition, the use of condition variables is always combined with a mutex lock.

1. create and log out

Like mutex lock, conditional variables have two static and dynamic creation methods. the static method uses the PTHREAD_COND_INITIALIZER constant, as shown below:

pthread_cond_t cond=PTHREAD_COND_INITIALIZER 

Call 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 attributes for condition variables, they are not implemented in LinuxThreads. Therefore, the cond_attr value is usually NULL and ignored.

To cancel a condition variable, you must call pthread_cond_destroy (). This condition variable can be canceled only when no thread is waiting for the condition variable. otherwise, EBUSY is returned. Because the condition variables implemented in Linux do not allocate any resources, the logout action only includes checking whether there are waiting threads. The API is defined as follows:
Int pthread_cond_destroy (pthread_cond_t * cond)

2. waiting and stimulating

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 waiting conditions: pthread_cond_wait () and pthread_cond_timedwait (). If the waiting time is not met before the given time, ETIMEOUT is returned and the waiting time ends, here, abstime appears in the absolute time format of the same meaning as the time () system call. 0 indicates GMT 00:00:00, January 1, January 1, 1970.

Either way, you must work with a mutex lock to prevent multiple threads from simultaneously requesting the Race Condition of pthread_cond_wait () (or pthread_cond_timedwait (), the same below ). Mutex locks must be common locks (PTHREAD_MUTEX_TIMED_NP) or adaptive locks (PTHREAD_MUTEX_ADAPTIVE_NP), and must be locked by this thread before pthread_cond_wait () is called ()), before the update condition is waiting for the queue, mutex remains locked and is unlocked before the thread is suspended and enters the waiting state. Before the condition is met and pthread_cond_wait () is left, mutex is re-locked to match the lock action before pthread_cond_wait.

There are two types of excitation conditions,Pthread_cond_signal ()Activate a thread waiting for this condition. when there are multiple waiting threads, activate one of them in the queue order.Pthread_cond_broadcast ()Activate all the waiting threads.

3. Miscellaneous

Pthread_cond_wait () and pthread_cond_timedwait () are both implemented as cancellation points. Therefore, the thread waiting for this point will immediately run again and leave pthread_cond_wait () after re-locking mutex (), then, cancel the operation. That is to say, if pthread_cond_wait () is canceled, mutex remains locked. Therefore, you need to define the exit callback function to unlock it.

The following example demonstrates the combination of mutex lock and condition variable, and the impact of cancellation on the conditional wait action. In this example, two threads are started and wait for the same condition variable. if you do not use the exit callback function (see the comments section in this example), tid2 will be in pthread_mutex_lock () wait permanently. If the callback function is used, the condition wait of tid2 and the condition excitation of the main thread can work normally.

#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);} 
   
  
 
# Include
 
  
# Include
  
   
# Include "stdlib. h "# include" unistd. h "pthread_mutex_t mutex; pthread_cond_t cond; void hander (void * arg) {free (arg); (void) pthread_mutex_unlock (& mutex);} void * thread1 (void * arg) {pthread_cleanup_push (hander, & mutex); while (1) {printf ("thread1 is running \ n"); pthread_mutex_lock (& mutex); pthread_cond_wait (& cond, & mutex ); printf ("thread1 applied the condition \ n"); pthread_mutex_unlock (& mutex ); Sleep (4);} pthread_cleanup_pop (0);} void * thread2 (void * arg) {while (1) {printf ("thread2 is running \ n "); pthread_mutex_lock (& mutex); pthread_cond_wait (& cond, & mutex); printf ("thread2 applied the condition \ n"); pthread_mutex_unlock (& mutex); sleep (1 );}} int main () {pthread_t thid1, thid2; printf ("condition variable study! \ N "); pthread_mutex_init (& mutex, NULL); pthread_cond_init (& cond, NULL); pthread_create (& thid1, NULL, thread1, NULL); pthread_create (& thid2, NULL, thread2, NULL); sleep (1); do {pthread_cond_signal (& cond);} while (1); sleep (20); pthread_exit (0); return 0;} # include
   
    
# Include
    
     
# Include "stdio. h "# include" stdlib. h "static pthread_mutex_t ctx = success; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; struct node {int n_number; struct node * n_next;} * head = NULL; static void cleanup_handler (void * arg) {printf ("Cleanup handler of second thread. /n "); free (arg); (void) pthread_mutex_unlock (& arg);} static void * thread_func (void * arg) {struct node * p = NULL; pthread_cleanup_push (cleanup_handler, p); while (1) {// This mutex is mainly used to ensure the concurrency of pthread_cond_wait pthread_mutex_lock (& CTX); while (head = NULL) {// This while clause should be noted that a single pthread_cond_wait function is very complete. // why does it need a while (head = NULL? Because the line/process in pthread_cond_wait may be accidentally awakened, if this time head! = NULL is not what we want. // At this time, the thread should be allowed to continue to enter pthread_cond_wait // pthread_cond_wait will first remove the previously-locked pthread_mutex_lock, and then block waiting for sleep in the column, wait until it is awakened again (in most cases, the waiting condition is set up // and the process is awakened. after the wake-up, the process will first lock the first pthread_mutex_lock (& CTX );, read the resource again // use this process to obtain a clear pthread_cond_wait (& cond, & CTX); p = head; head = head-> n_next; printf ("Got % d from front of queue/n", p-> n_number); free (p);} pthread_mutex_unlock (& CTX); // the data operation in the critical section is completed, release mutex lock} pthread_cleanup_pop (0); return 0 ;} Int main (void) {pthread_t tid; int I; struct node * p; // The sub-thread will always wait for resources, similar to producers and consumers, but here the consumer can be multiple consumers, and // not only supports a single common consumer, this model is simple, but very powerful pthread_create (& tid, NULL, thread_func, NULL ); sleep (1); for (I = 0; I <10; I ++) {p = (struct node *) malloc (sizeof (struct node )); p-> n_number = I; pthread_mutex_lock (& CTX); // you need to operate the head critical resource. lock the resource first. p-> n_next = head; head = p; pthread_cond_signal (& cond); pthread_mutex_unloc K (& CTX); // unlock sleep (1);} printf ("thread 1 wanna end the line. so cancel thread 2. /n "); // for pthread_cancel, there is an additional description. it is used to end the child thread from the external end. The child thread will exit at the nearest cancellation point. // thread, in our code, the recent cancellation point must be pthread_cond_wait. Pthread_cancel (tid); pthread_join (tid, NULL); printf ("All done -- exiting/n"); return 0 ;}
    
   
  
 

If you do not comment 5 on the pthread_cancel () action, child1 and child2 work normally even if there are no sleep () delayed operations. Note 3 and Note 4 delay makes child1 have time to complete the cancellation action, so that child2 can enter the request lock operation after child1 exits. If the callback function definition of comment 1 and comment 2 is not defined, the system will suspend the child2 request lock. if the latency of comment 3 and comment 4 is not included at the same time, child2 can be controlled before child1 completes the cancellation action, so that the lock application operation can be smoothly executed, but it may be suspended in pthread_cond_wait (), because there are also mutex application operations. The child1 function provides the usage of standard condition variables: callback function protection, locking before the condition, and unlocking after pthread_cond_wait () is returned.

The conditional variable mechanism is not asynchronous signal security, that is, calling pthread_cond_signal () or pthread_cond_broadcast () in the signal processing function may cause a deadlock.

3. signal lights

The main difference between traffic lights and mutex locks and condition variables lies in the concept of "lights". when lights are on, resources are available, and when lights are off, they are unavailable. If the synchronization mode in the last two phases focuses on "waiting" operations, that is, the resource is unavailable, the traffic signal mechanism focuses on lighting, that is, to inform the resource availability; it makes no sense to unlock a thread without waiting for it or to stimulate the conditions. The lighting operation of a thread without waiting for it is effective and can be kept on. Of course, such operation primitives also mean more overhead.

In addition to the binary lamp, the application of traffic signals can also use a lamp number greater than 1 to indicate that the number of resources is greater than 1.

1. create and log out

POSIX signal lights are defined as well-known signal lights and unknown signal lights, but the implementation of LinuxThreads only has unknown lights. besides being well-known lights, they can always be used between multiple processes, there is no major difference between the use of lamps and lamps, so we will only discuss lamps.

int sem_init(sem_t *sem, int pshared, unsigned int value)
This is the API for creating traffic signals. value indicates the initial value of the traffic signal. pshared indicates whether to share the traffic signal with multiple processes rather than just one process. LinuxThreads does not implement multi-process shared traffic signals. Therefore, all non-0 pshared inputs will return-1 for sem_init () and set errno to ENOSYS. The initialized signal lights are characterized by sem variables and used for the following lighting and lamp removal operations.
int sem_destroy(sem_t * sem) 
The canceled signal sem requires that no thread is waiting for the signal. otherwise,-1 is returned and errno is set to EBUSY. In addition, threads's signal cancellation function does not perform any other action.

2. lighting and lighting removal

int sem_post(sem_t * sem) 

The light-on operation adds 1 to the value of the signal lamp, indicating that an accessible resource is added.

int sem_wait(sem_t * sem)int sem_trywait(sem_t * sem) 

Sem_wait () is to wait for the light to shine, wait for the light to shine (the signal light value is greater than 0), then reduce the signal light by 1 and return. Sem_trywait () is a non-blocking version of sem_wait (). If the traffic signal count is greater than 0, 1 is reduced and 0 is returned. otherwise,-1 is returned immediately, and errno is set to EAGAIN.

3. get the lamp value

int sem_getvalue(sem_t * sem, int * sval) 

Read the lamp count in sem, save it in * sval, and return 0.

4. Miscellaneous

Sem_wait () is implemented as a cancellation point, and in the architecture that supports atomic "comparison and exchange" commands, sem_post () it is the only POSIX asynchronous signal security API that can be used for asynchronous signal processing functions.

    #include 
 
      #include 
  
       #include 
   
        #include 
    
         #include 
     
          #include 
      
        #define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;} typedef struct _PrivInfo { sem_t s1; sem_t s2; time_t end_time; }PrivInfo; static void info_init (PrivInfo* thiz); static void info_destroy (PrivInfo* thiz); static void* pthread_func_1 (PrivInfo* thiz); static void* pthread_func_2 (PrivInfo* thiz); int main (int argc, char** argv) { pthread_t pt_1 = 0; pthread_t pt_2 = 0; int ret = 0; PrivInfo* thiz = NULL; thiz = (PrivInfo* )malloc (sizeof (PrivInfo)); if (thiz == NULL) { printf ("[%s]: Failed to malloc priv./n"); return -1; } info_init (thiz); ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, thiz); if (ret != 0) { perror ("pthread_1_create:"); } ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, thiz); if (ret != 0) { perror ("pthread_2_create:"); } pthread_join (pt_1, NULL); pthread_join (pt_2, NULL); info_destroy (thiz); return 0; } static void info_init (PrivInfo* thiz) { return_if_fail (thiz != NULL); thiz->end_time = time(NULL) + 10; sem_init (&thiz->s1, 0, 1); sem_init (&thiz->s2, 0, 0); return; } static void info_destroy (PrivInfo* thiz) { return_if_fail (thiz != NULL); sem_destroy (&thiz->s1); sem_destroy (&thiz->s2); free (thiz); thiz = NULL; return; } static void* pthread_func_1 (PrivInfo* thiz) { return_if_fail(thiz != NULL); while (time(NULL) < thiz->end_time) { sem_wait (&thiz->s2); printf ("pthread1: pthread1 get the lock./n"); sem_post (&thiz->s1); printf ("pthread1: pthread1 unlock/n"); sleep (1); } return; } static void* pthread_func_2 (PrivInfo* thiz) { return_if_fail (thiz != NULL); while (time (NULL) < thiz->end_time) { sem_wait (&thiz->s1); printf ("pthread2: pthread2 get the unlock./n"); sem_post (&thiz->s2); printf ("pthread2: pthread2 unlock./n"); sleep (1); } return; }
      
     
    
   
  
 

IV. asynchronous signal

Because LinuxThreads is implemented by a lightweight process outside the kernel, kernel-based asynchronous signal operations are also effective for threads. But at the same time, because the asynchronous signal is always actually sent to a process, it is impossible to implement the POSIX standard requirement to "signal to a process, then, the process distributes the signal to all threads that do not block the signal. "The primitive affects only one thread.

POSIX asynchronous signal is also a function provided by the standard C library, mainly including 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 (), etc, they work with functions such as kill () for sending signals to implement asynchronous signal functions between processes. LinuxThreads encapsulates sigaction () he raise () around the thread. This section focuses on the extended asynchronous signal functions in LinuxThreads, including pthread_sigmask (), pthread_kill (), and sigwait () three functions. Undoubtedly, all POSIX asynchronous signal functions are available to threads.

Int pthread_sigmask (int how, const sigset_t * newmask, sigset_t * oldmask)
Sets the thread's signal shielding code. the semantics is the same as sigprocmask (), but the Cancel signals that are not blocked and the Restart signals that are not allowed to respond are protected. Blocked signals are stored in the signal queue and can be taken out by the sigpending () function.

Int pthread_kill (pthread_t thread, int signo)
Sends a signo signal to the thread. After the corresponding process number is located through the thread number, the kill () system call is used to complete sending.

Int sigwait (const sigset_t * set, int * sig)
Suspends a thread, waits for one of the signals specified in the set to arrive, and stores the signals to * sig. POSIX standard recommends that all threads in the process shield the signal before calling the sigwait () wait signal to ensure that only the sigwait () caller obtains the signal, for asynchronous signals waiting for synchronization, pthread_sigmask () should always be called to shield the signal before any threads are created. In addition, the signal processing function attached to the signal will not be called when sigwait () is called.

If the Cancel signal is received during the waiting period, exit the waiting immediately, that is, the sigwait () is implemented as the canceling point.

V. Other synchronization methods

In addition to the synchronization methods discussed above, many other inter-process communication methods are also available for LinuxThreads, such as file system-based IPC (pipelines, Unix domain sockets, etc.) and message queue (Sys. V or Posix) and System V signal lights. Note that LinuxThreads is viewed in the kernel as an independent process of shared storage area, shared file system attributes, shared signal processing, and shared file descriptor.

VI. differences between conditional variables, mutex locks, and semaphores

1). the mutex lock must always be unlocked by the lock thread. when the semaphore is mounted, it does not need to be executed by the same process that has executed its waiting operation. One thread can wait for a given signal lamp, while the other thread can mount the signal lamp.

2). mutex lock is either locked or unlocked (binary state, type binary semaphore)

3). because a Semaphore has a state associated with it (its count value), the semaphore hanging-out operation is always remembered. However, when sending a signal to a condition variable, if no thread is waiting on the condition variable, the signal will be lost.

4 ). mutex lock is designed for locking. conditional variables are designed for waiting. signals can be used for locking or waiting, which may lead to more overhead and higher complexity.

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.