Thread synchronization and mutual exclusion: mutual exclusion lock

1. What is thread synchronization and mutual exclusion?

Mutual exclusion : It means that only one thread is allowed to run the program slice at a certain time, which is exclusive and unique. 
For thread A and thread B, at the same time, only one thread is allowed to operate on critical resources, that is, when A enters the critical area to operate on resources, B must wait; when A finishes executing and exits the critical area, B To operate on critical resources.
Synchronization : refers to the realization of ordered access between processes on the basis of mutual exclusion. Suppose there are existing thread A and thread B, thread A needs to write data to the buffer, and thread B needs to read data from the buffer, but there is a restrictive relationship between them, that is, when thread A writes, B cannot come to get the data ;A cannot write to the buffer while B is taking the data, that is to say, only when A has finished writing the data (or B has taken the data), can B read the data (or A can write data into it). This relationship is a thread synchronization relationship.

2. What are critical resources and critical regions?

Critical resources : are data/resources that can be shared by multiple threads.
Critical section : refers to the piece of code that operates on critical resources.
In multi-threaded programming, it is inevitable that multiple threads will access critical resources at the same time. If it is not protected, the result will definitely not be as expected. Take a look at the following code:

static int g_val=0;
void* pthread_mem(void* arg)
{
    int i=0;
    int val=0;
    while(i<500000)
    {
        val = g_val;
        i++;
        g_val=val+1;
    }
    return NULL;
}

int main()
{
    pthread_t tid1;
    pthread_t tid2;
    pthread_create(&tid1, NULL, pthread_mem, NULL);
    pthread_create(&tid2, NULL, pthread_mem, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("g_val end is :%d\n",g_val);
    return 0;
}

Both thread 1 and thread 2 need to perform +1 operation on g_val, looping 500,000 times, because each time +1 operation on g_val is not completed in one step (atomic operation), it may be cut at any time during the execution of a thread Go out to let another thread operate. Suppose thread 1 is cut out while it is executing. At this time, it has accumulated g_val to 3000, but thread 2 does not know it when it cuts in. It may start to accumulate g_val from 0. We expect that thread 1 and thread 2 can accumulate g_val to 100 0000, but the actual result is like this: obviously 
 
, the result is a random number. . .

3. Implementation of thread synchronization and mutual exclusion

Mutex (Mutex) 
1) The essence of the mutex: 
First of all, it needs to be clear that the mutex is actually a variable. When using the mutex, it is actually to set this variable to 0 or 1. And make a judgment so that the thread can acquire the lock or release the lock. (The specific implementation of the mutex is explained at the end of the article) 
2) Function: The function of the mutex is to protect the critical section so that only one thread can execute the code in the critical section at any time. Mutual exclusion between multiple threads is realized. 
3) Interface: 
The use of mutex mainly has the following interface operations:

//两种方法对锁进行初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
//互斥锁的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

//获得锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

//释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

Comparison of two lock acquisition methods: 
pthread_mutex_lock: If another thread has already acquired the lock at this time, the current thread will be suspended and wait after calling this function until another thread releases the lock, and the thread will be locked. wake. 
pthread_mutex_trylock: If another thread has acquired the lock at this time, the current thread will return immediately after calling this function, and return and set the error code to EBUSY, that is, it will not cause the current thread to hang and wait.

Now that we have a mutex, we can modify the above code as follows:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int g_val=0;
void* pthread_mem(void* arg)
{
    int i=0;
    int val=0;
    while(i<500000)
    {
        pthread_mutex_lock(&mutex);
        val = g_val;
        i++;
        g_val=val+1;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

This code locks and unlocks the critical section, each thread obtains a lock when entering the critical section, and releases the lock resource after the operation is completed, so that other waiting threads can lock and enter, thus ensuring that every The operations performed by each thread are atomic, so that the final result is 1000000.

4. The underlying implementation of the mutex

As mentioned above, the essence of mutex is a kind of variable. Assuming that mutex is 1, it means that the lock is free. At this time, a thread can obtain the lock resource if it calls the lock function; when mutex is 0, it means that the lock is occupied by other threads. If a thread calls lock to obtain the lock at this time, it will is hung waiting.

1) Implementation scheme 1 of lock and unlock 

Write picture description here
unlock: This operation is atomic, that is, after executing the unlock code, the mutex is either 1 or not 1 
lock: When executing the lock, the mutex must be judged first, if mutex>0, modify mutex=0, otherwise Indicates that the lock is occupied, and the current process is suspended to wait. Suppose the mutex is 1, and there are two threads A and B to lock to obtain the lock. For A and B, both of them get the mutex as 1, and they will enter the if() condition. At this time, thread A has already The lock is acquired (mutex is set to 0), but thread B does not know, and mutex is also set to 0. Therefore, both thread A and thread B will think that they have acquired the lock. For this scheme, because the lock process is not atomic, errors will also occur.

2) Implementation scheme 2 of lock and unlock 
uses the swap or exchange instruction. The meaning of this instruction is to exchange the data in the register and the memory unit. This instruction ensures the atomicity of the operation: lock: In this step, assign 0 
 
to Register al, then exchange the value in mutex with al, and then make a judgment. When a lock operation is performed on a thread, there is no problem even if it is cut out at any step in the middle. This ensures that lock operations are also atomic.

5. Problems introduced by using mutexes

Using mutexes can cause deadlock problems.

Deadlock: 
It refers to a permanent waiting state in which each thread in a group of threads occupies resources that will not be released, but is in a permanent waiting state due to mutual application for resources that are occupied by other threads and will not be released. In layman's terms, assuming that thread A holds lock a, thread B holds lock b, and the condition for a thread to access the critical section is to have both lock a and lock b, then A will wait for B to release lock b, and B will wait for A To release lock a, if there is no measure, the two will wait forever, thus creating a deadlock.
The situation of deadlock 
1. Insufficient system resources : If the system resources are sufficient, each thread that applies for a lock can obtain the lock, then the situation of deadlock will be greatly reduced; 
2. The order of applying for locks is improper : when two threads Deadlocks can also occur when applying and releasing lock resources in different orders. (Execute the following code yourself to observe the phenomenon.)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int a=0;
int b=0;
pthread_mutex_t mutex_a;
pthread_mutex_t mutex_b;

void *another(void* arg)
{
    pthread_mutex_lock(&mutex_b);
    printf("new_thread,got mutex_b,waiting for mutex_a\n");
    sleep(5);
    ++b;
    pthread_mutex_lock(&mutex_a);
    b += a++;
    pthread_mutex_unlock(&mutex_a);
    pthread_mutex_unlock(&mutex_b);
    pthread_exit(NULL);
}

int main()
{
    pthread_t id;
    pthread_mutex_init(&mutex_a, NULL);
    pthread_mutex_init(&mutex_b, NULL);
    pthread_create(&id, NULL, another, NULL);

    pthread_mutex_lock(&mutex_a);
    printf("main_thread,got mutex_a,waiting for mutex_b\n");
    sleep(5);
    ++a;
    pthread_mutex_lock(&mutex_b);
    a += b++;
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);

    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex_a);
    pthread_mutex_destroy(&mutex_b);
    return 0;
}

In the above code, the main thread first applies for mutex_a, then applies for mutex_b, and in the new thread, the new thread first applies for mutex_b, and then applies for mutex_a, so that both parties hold a lock and wait for each other's lock, resulting in deadlock. The execution results are as follows: 

Write picture description here
Conditions for deadlock 
1. Mutual exclusion attribute : that is, only one thread can occupy resources at a time. 
2. Request and hold : That is, threads that have already applied for lock resources can continue to apply. In this case, a thread can also generate a deadlock situation, that is, holding the lock to find the lock. 
3. Inalienable : The thread has obtained all resources, and it cannot be forcibly deprived until it is released by itself. 
4. Loop waiting : Multiple threads form a loop waiting, and each thread is waiting for the lock resource of the adjacent thread.
Avoidance of deadlock: 
1. Since deadlock is caused by the use of locks, try not to use locks if you can. If there are multiple solutions that can be realized, then try not to use locks plan.
2. Try to avoid acquiring multiple locks at the same time. If necessary, ensure that the locks are acquired in the same order.
--------------------- 
Reprinted from: https://blog.csdn.net/qq_33951180/article/details/72801228 

Guess you like

Origin blog.csdn.net/my8688/article/details/85195610