1. Basic concepts
Mutex and synchronization are the most basic logical concepts:
- Mutual exclusion refers to controlling two processes so that they are mutually exclusive and do not run at the same time.
- Synchronization refers to controlling two progresses so that they come first and then come first, and the order is controllable.
2. Mutex basic logic
The easiest way to make mutual exclusion between multiple threads is to add a mutex. Any line that wants to start running the code in the mutex interval must first acquire the mutex lock, and the essence of the mutex lock is a binary semaphore, so when one of the threads preemptively acquires the mutex lock, the rest of the threads will be locked. It cannot be acquired again. The effect is equivalent to adding a lock to the relevant resource. Only when the user actively unlocks it can other threads have the opportunity to acquire the lock.
Mutex usage scenario: When we use some critical resources to prevent multiple threads from accessing at the same time, we can do this. Before accessing critical resources, let the threads lock first, then access the resources, and unlock them after accessing, so that Other threads go to lock. Description: Critical resources: shared resources (resources that need to be operated together between multiple threads)
The function interface description of the mutex is as follows:
1) Define mutex variable -> data type: pthread_mutex_t ---> global variable pthread_mutex_t m; 2) Initialize the mutex -> pthread_mutex_init() #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); parameter: mutex: the address of the uninitialized mutex variable mutexattr: common attribute, NULL return value: Success: 0 Failure: non-zero error code Static initialization: pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; That is, the above sentence is equivalent to: pthread_mutex_t m; pthread_mutex_init( &m,NULL); 3) Lock it. -> pthread_mutex_lock() #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); parameter: mutex: the address of the mutex variable return value: Success: 0 Failure: non-zero error code 4) Unlock -> pthread_mutex_unlock() #include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex); parameter: mutex: the address of the mutex variable return value: Success: 0 Failure: non-zero error code 5) Destroy the mutex. -> pthread_mutex_destroy() #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); parameter: mutex: the address of the mutex variable return value: Success: 0 Failure: non-zero error code
The test code is as follows:
#include<stdio.h> #include <sys/types.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <sys/sem.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> int g_val = 10; //1, first define a mutex variable pthread_mutex_t mutex; //Static initialization //pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; void* start_routine1(void*arg) { //Lock first when accessing shared resources pthread_mutex_lock( & mutex); g_val = 20; sleep(1); printf("start_routine1 g_val:%d\ ",g_val); // Shared resources need to be unlocked at the end of use pthread_mutex_unlock( & mutex); } void* start_routine2(void*arg) { //When accessing shared resources, lock it first -- if you don't get the lock, it will block and wait for the lock pthread_mutex_lock( & mutex); g_val = 200; sleep(2); printf("start_routine2 g_val:%d\ ",g_val); // Shared resources need to be unlocked at the end of use pthread_mutex_unlock( & mutex); } int main() { //2. Initialize the mutex pthread_mutex_init( & mutex, NULL); //1, child thread 1 pthread_t thread1; pthread_create( & thread1, NULL, start_routine1, NULL); //2, child thread 2 pthread_t thread2; pthread_create( & thread2, NULL, start_routine2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); //destroy the lock pthread_mutex_destroy( & mutex); return 0; }
3. Basic logic of read-write lock
For mutexes, all accesses involving critical resources are locked, which will waste a lot of time in the scenario of concurrent read operations. In order to improve access efficiency, it is necessary to distinguish read and write operations on resources: read operations can be executed concurrently by multiple tasks, and only write operations can be properly mutually exclusive. This is the design source of the read-write lock.
1. Defects of mutex
Whether the mutex is to read shared resources or modify shared resources, it must be locked, but during the locking period, it cannot be locked by other threads.
2. Advantages of read-write lock
Access resources (read a book together) -> read lock at the same time -> read lock is a shared lock.
Modify resources -> cannot write lock at the same time -> write lock is a mutex.
This lock that has both a read lock and a write lock is called a read-write lock.
The function interface of the read-write lock is described as follows:
1) Define a read-write lock variable (data type: pthread_rwlock_t) pthread_rwlock_t rwlock; (rwlock=read write lock) 2) Initialize the read-write lock----"man 3 pthread_rwlock_init #include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr); parameter: rwlock: the address of the read-write lock variable attr: attribute, usually NULL return value: Success: 0 Failure: non-zero error code 3) Read the lock and lock it. --->man 3 pthread_rwlock_rdlock (rdlock = read lock) #include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); parameter: rwlock: the address of the read-write lock variable 4) Write lock lock --- "pthread_rwlock_wrlock (wrlock = write lock) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); parameter: rwlock: the address of the read-write lock variable 5) Read-write lock unlock --- "pthread_rwlock_unlock (the unlock function of write lock and read lock is the same) #include <pthread.h> int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); parameter: rwlock: the address of the read-write lock variable 6) Destroy the read-write lock. ----"pthread_rwlock_destroy #include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); parameter: rwlock: the address of the read-write lock variable
Read-write lock test code is as follows:
#include <stdio.h> #include <sys/types.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <sys/sem.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> //Define a read-write lock variable pthread_rwlock_t rwlock; int g_val = 10; void *start_routine1(void*arg) { pthread_rwlock_wrlock( & amp;rwlock);//add write lock g_val = 20; sleep(3); printf("start_routine1: %d\ ",g_val); pthread_rwlock_unlock( & amp;rwlock); //unlock } void *start_routine2(void*arg) { pthread_rwlock_wrlock( & amp;rwlock);//add write lock g_val = 200; sleep(3); printf("start_routine2: %d\ ",g_val); pthread_rwlock_unlock( & amp;rwlock); //unlock } void *start_routine3(void*arg) { pthread_rwlock_rdlock( & amp;rwlock);//add read lock //get data access (read) shared resource but not modify (write) int cnt=5; while(cnt--){ sleep(1); printf("start_routine3: %d\ ",g_val); } pthread_rwlock_unlock( & amp;rwlock); //unlock } void *start_routine4(void*arg) { pthread_rwlock_rdlock( & amp;rwlock);//add read lock // Get data access (read) shared resources but not modify (write) int cnt=5; while(cnt--){ sleep(1); printf("start_routine4: %d\ ",g_val); } pthread_rwlock_unlock( & amp;rwlock); //unlock } int main() { //Initialize read-write lock pthread_rwlock_init( &rwlock,NULL); pthread_t thread1; pthread_create( & thread1, NULL, start_routine1, NULL); pthread_t thread2; pthread_create( & thread2, NULL, start_routine2, NULL); pthread_t thread3; pthread_create( & thread3, NULL, start_routine3, NULL); pthread_t thread4; pthread_create( & thread4, NULL, start_routine4, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, NULL); pthread_join(thread4, NULL); //Destroy the read-write lock pthread_rwlock_destroy( & rwlock); return 0; }
4. Basic concepts of condition variables
In many occasions, the execution of the program usually needs to meet certain conditions. When the conditions are immature, the task should enter sleep blocking waiting, and when the conditions are ripe, the task should be able to be quickly awakened. In addition, in a concurrent program, other tasks will access the condition at the same time, so the condition must be accessed in a mutually exclusive manner at any time. The conditional quantity is a logic mechanism that specifically solves the above scenarios.
Note that in the above statement, conditions and conditional quantities are two different things. The so-called condition refers to the preconditions required for the program to continue running, such as whether the file has been read, whether the memory is cleared, and other specific scene restrictions, while the conditional quantity ( That is, pthread_cond_t) is a synchronization mutex variable to be discussed in this courseware, which is dedicated to solving the above logic scenarios.
Description:
- Before making a conditional judgment, lock it first (to prevent concurrent access by other tasks)
- After the lock is successfully locked, determine whether the condition is allowed
- If conditions permit, directly operate the critical resource, and then release the lock
- If the condition is not allowed, enter the waiting queue of the condition quantity to sleep, and release the lock at the same time
- The task sleeping in the condition quantity can be woken up by other tasks. When waking up, re-determine whether the condition allows the program to continue to execute. Of course, it must be locked first.
The function interface description of the condition variable is as follows:
1) Define a condition variable first. -> Data type: pthread_cond_t pthread_cond_t cond; 2) Initialize the condition variable #include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //Static initialization parameter: cond: the address of the condition variable cond_attr common attribute, NULL. return value: Success: 0 Failure: non-zero error code 3) How to enter the condition variable and wait (two functions: 1. Blocking and waiting 2. Automatic unlocking) int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); parameter: cond: the address of the condition variable mutex: The address of the mutex -> enter the condition variable and it will be automatically unlocked. return value: Success: 0 Failure: non-zero error code 4) How to wake up the thread waiting in the condition variable? -> When the thread leaves the condition variable, it will be automatically locked. #include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond);//Broadcast: wake up all threads waiting in the condition variable int pthread_cond_signal(pthread_cond_t *cond);//unicast: randomly wake up a thread waiting in the condition variable parameter: cond: the address of the condition variable return value: Success: 0 Failure: non-zero error code 5) Destroy the condition variable----"man 3 pthread_cond_destroy #include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); parameter: cond: the address of the condition variable
5. The use of conditional quantities
Conditional quantities are generally used in conjunction with mutexes (or binary semaphores). Mutex locks provide the function of locking critical resources, and conditional quantities provide the function of blocking sleep and wakeup.
The sample code is as follows:
#include <stdio.h> #include <sys/types.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <signal.h> #include <sys/sem.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> /* Exercise: There are 4 children, and the task of each child is to receive 1000 living expenses. Before going back to school, the parents deposit 2000 yuan in the bank card first, 2 threads exit after receiving 1000 yuan, and the other 2 threads wait in the condition variable, and the father pays 1000 more to wake up all the children Get up to get the money, after a while, make another 1,000 yuan, and then wake up the last child to get up and get the money and leave for school. */ int g_money = 2000; //Define a mutex variable pthread_mutex_t mutex; //Define a condition variable and initialize it statically pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void *start_routine(void*arg) { printf("[%lu] child thread start\ ", pthread_self()); pthread_mutex_lock( & amp;mutex);//Locking strength should be small //When the condition is not met, enter the condition variable and wait while(g_money<1000){ printf("There is no money, go to the condition variable and wait for the parents to send money and notify.....\ "); //Automatically unlock, and block waiting pthread_cond_wait( &cond, &mutex); printf("Parents have sent money and have notified me, the balance at this time: %d\ ", g_money); } //Come here, it means you have money g_money -= 1000; printf("[%lu] sub-thread got money, bank card balance: %d\ ", pthread_self(), g_money); \t\t pthread_mutex_unlock( & amp;mutex); //unlock printf("[%lu] sub-thread end\ ", pthread_self()); //Take the money and leave pthread_exit(NULL); } int main() { //Initialize the mutex pthread_mutex_init( & mutex, NULL); pthread_t thread1; //i pthread_create( & thread1, NULL, start_routine, NULL); pthread_t thread2; //brother pthread_create( & thread2, NULL, start_routine, NULL); pthread_t thread3; //sister pthread_create( & thread3, NULL, start_routine, NULL); pthread_t thread4; //brother pthread_create( & thread4, NULL, start_routine, NULL); int cnt=5; while(cnt--){ sleep(1); printf("The main thread (parent) is about to send money...%d\ ", cnt); } //Main thread (parent) play money pthread_mutex_lock( & amp;mutex);//lock g_money + =1000; pthread_mutex_unlock( & amp;mutex); //unlock pthread_cond_broadcast( & amp;cond);//Broadcast: wake up all threads waiting in the condition variable cnt=5; while(cnt--){ sleep(1); printf("The main thread (parent) is about to send money...%d\ ", cnt); } pthread_mutex_lock( & amp;mutex);//lock g_money + =1000; pthread_mutex_unlock( & amp;mutex); //unlock pthread_cond_signal( & amp;cond);//Unicast: Randomly wake up a thread waiting in the condition variable pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, NULL); pthread_join(thread4, NULL); pthread_mutex_destroy( & mutex); pthread_cond_destroy( & amp;cond); return 0; }
The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge