[learning notes on multithreaded programming 8] thread synchronization using mutex

Posted by Atomic Taco on Wed, 19 Jan 2022 11:49:16 +0100

Statement: This study note is a summary based on the tutorial and combined with your own learning situation. It is not original. If you want to see the original version, please see the C language Chinese network Multithreaded programming (C language + Linux) , the website has many good programming learning tutorials, especially about C language.

In< Thread synchronization mechanism >As mentioned in the first section, there are four common methods to realize multithread synchronization, and mutex is the simplest and most effective method. In this section, we will explain the specific usage of mutex in detail.

The core idea of mutex to realize multithread synchronization is that when a thread accesses the public resources in the process space, the thread performs the "lock" operation (locking the resources) to prevent other threads from accessing. After the access is completed, the thread is responsible for completing the "unlock" operation and transferring resources to other threads. When multiple threads want to access resources, whoever completes the "lock" operation first will access resources first.

When multiple threads want to access the public resources in the "locked" state, they can only wait for the resources to be "unlocked", and all threads will form a waiting (blocking) queue. After the resource is unlocked, the operating system will wake up all threads in the waiting queue. The first thread accessing the resource will "lock" the resource first, and other threads will continue to wait.

In essence, a mutex is a global variable. It has only two values of "lock" and "unlock", meaning:

  • "Unlock" means that the current resource can be accessed. The first thread accessing the resource is responsible for changing the value of the mutex to "lock", and then reset to "unlock" after the access is completed;
  • "lock" indicates that a thread is accessing resources. Other threads need to wait for the value of the mutex to be "unlock" before they can start accessing.

By "locking" and "unlock ing" the resource, you can ensure that at most one thread accesses the resource at the same time, which fundamentally avoids the occurrence of "multi thread grabbing resources".

Again, the same thread must be used to "lock" and "unlock" resources. In other words, the thread that performs the "lock" operation on the resource must also be responsible for the "unlock" operation.

Usage of mutex

POSIX standard stipulates that pthread is used_ mutex_ A mutex is represented by a variable of type T, which is defined in < pthread h> In the header file. for instance:

pthread_mutex_t myMutex;

We have successfully defined a mutex named myMutex, but we have to initialize it to use it.

1) Initialization of mutex

Initialize pthread_ mutex_ There are two ways of T variable, namely:

//1. Use specific macros
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
//2. Call the initialized function
pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex , NULL);

The above two initialization methods are completely equivalent, PTHREAD_MUTEX_INITIALIZER macro and pthread_mutex_init() functions are defined in < pthread h> In header files, their main differences are:

  • pthread_ mutex_ The init () function can customize the attributes of the mutex (the specific customized methods will not be explained here).
  • For the mutex that calls malloc() function to allocate dynamic memory, the initialization can only be completed by the second method;

pthread_ mutex_ The init() function is specially used to initialize the mutex. The syntax format is as follows:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

Mutex parameter indicates the mutex to be initialized; The attr parameter is used to customize the attribute of the newly created mutex. When the value of attr is NULL, it means that the mutex is created with the default attribute.

pthread_ mutex_ When the init() function successfully completes the initialization operation, it returns the number 0; If initialization fails, the function returns a non-zero number.

Note that you cannot initialize a mutex that has already been initialized, otherwise unexpected errors will occur in the program.

2) "Locking" and "unlocking" of mutex

For the "lock" and "unlock" operations of mutex, there are three common functions:

int pthread_mutex_lock(pthread_mutex_t* mutex);   //Realize locking
int pthread_mutex_trylock(pthread_mutex_t* mutex);  //Realize locking
int pthread_mutex_unlock(pthread_mutex_t* mutex);   //Realize unlocking

The mutex parameter represents the mutex we want to manipulate. When the function is executed successfully, it returns the number 0; otherwise, it returns a non-zero number.

pthread_ mutex_ The unlock() function is used to "unlock" the specified mutex, pthread_mutex_lock() and pthread_ mutex_ The trylock() function is used to implement the "lock" operation. The difference is that when the mutex is already in the "lock" state:

  • Execute pthread_ mutex_ The lock () function will make the thread enter the waiting (blocking) state until the mutex is released;
  • Execute pthread_ mutex_ The trylock() function does not block the thread and directly returns a non-zero number (indicating locking failure).

3) Destruction of mutex

For mutexes created using dynamic memory, for example:

pthread_mutex_t myMutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));pthread_mutex_init(&myMutex , NULL);

Pthread must be called before manually releasing the memory occupied by myMutex (calling the free() function)_ mutex_ The destroy() function destroys the object.

pthread_ mutex_ The destroy() function is used to destroy the created mutex. The syntax format is as follows:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

The mutex parameter indicates the mutex to destroy. If the function successfully destroys the specified mutex, it returns the number 0, otherwise it returns a non-zero number.

Note that for PTHREAD_MUTEX_INITIALIZER or pthread_ mutex_ The init() function directly initializes the mutex without calling pthread_ mutex_ The destroy() function destroys manually.

Practical application of mutex

Next, we use mutex pairs< Thread synchronization mechanism >The simulation procedure of "4 conductors selling 10 tickets" in Section 1 is improved as follows:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int ticket_sum = 10;
//Create mutex
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
//Simulated conductor selling tickets
void *sell_ticket(void *arg) {
    //Outputs the thread ID of the currently executing function
    printf("Current thread ID: %u\n", pthread_self());
    int i;
    int islock = 0;
    for (i = 0; i < 10; i++)
    {
        //Current thread "locked"
        islock = pthread_mutex_lock(&myMutex);
        //If "lock" is successful, execute the following code
        if (islock == 0) {
            //If the number of votes > 0, start selling tickets
            if (ticket_sum > 0)
            {
                sleep(1);
                printf("%u Sell first %d Ticket\n", pthread_self(), 10 - ticket_sum + 1);
                ticket_sum--;
            }
            //The current thread simulates the ticket selling process and performs the "unlock" operation
            pthread_mutex_unlock(&myMutex);
        }
    }
    return 0;
}
int main() {
    int flag;
    int i;
    void *ans;
    //Create 4 threads to simulate 4 conductors
    pthread_t tids[4];
    for (i = 0; i < 4; i++)
    {
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) {
            printf("Thread creation failed!");
            return 0;
        }
    }
    sleep(10);   //Wait for 4 threads to complete execution
    for (i = 0; i < 4; i++)
    {
        //Block the main thread and confirm that the execution of four threads is completed
        flag = pthread_join(tids[i], &ans);
        if (flag != 0) {
            printf("tid=%d Waiting failed!", tids[i]);
            return 0;
        }
    }
    return 0;
}

A total of four threads are created in the program. Each thread will perform the "lock" operation (line 17) before "selling tickets", and then perform the "unlock" operation (line 28) after "selling tickets". It can be seen from the execution results that the mutex lock solves the problem of "competing for resources among threads" and realizes thread synchronization.

Topics: C Linux Multithreading Concurrent Programming POSIX