Lock|Multithreading|Solve data inconsistency caused by parallel computing|Thread mutual exclusion|Critical resource protectionSuper detailed explanation and code comments

Foreword

Then, the blogger here will first check out some columns full of dry goods!

Hand tear data structure https:// blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014. 3001.5482 This contains a lot of data structure learning summaries of bloggers, Each article is written with super heart, and interested partners should support it!
Algorithm column https://blog.csdn.net /yu_cblog/category_11464817.htmlhttps://blog.csdn.net/yu_cblog/category_11464817.html Here is the STL source code analysis column, which will continue to update the simulation implementation of various STL containers.

STL source code Analysis https://blog.csdn.net/yu_cblog/category_11983210.html?spm=1001.2014.3001.5482https://blog.csdn.net/yu_cblog/category_11983210.html ?spm=1001.2014.3001.5482

Why mutexes are needed

In order to explain clearly why the lock is needed, the blogger shows you through a piece of code.

This is a piece of code for multi-thread ticket grabbing logic:

There are a total of tickets tickets, we create three threads to grab tickets

According to the principle: when the tickets are sold out, the number of votes should be 0

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <vector>

// If multiple threads access the same global variable and perform data calculations on it, will multiple threads affect each other?
int tickets = 10000; // 10000 here is the critical resource

void *getTickets(void *args)
{
    (void) args;
    while (true)
    {
        if (tickets > 0)
        {
            usleep(1000);
            printf("%p: %d\\
", pthread_self(), tickets);
            tickets--;
        }
        else
        {
            // no more tickets
            break;
        }
    }
}
int main()
{
    pthread_t t1, t2, t3;
    // The logic of multi-thread grabbing tickets
    pthread_create( & amp;t1, nullptr, getTickets, nullptr);//Create thread
    pthread_create( &t2, nullptr, getTickets, nullptr);
    pthread_create( &t3, nullptr, getTickets, nullptr);
    pthread_join(t1, nullptr);//Thread waiting
    pthread_join(t2, nullptr);
    pthread_join(t3, nullptr);

    return 0;
}

Phenomena

In the end, I got -1, which is unreasonable!

In fact, a sentence in the code actually corresponds to more than one sentence in assembly language. The thread may be switched by the scheduler at any time. At this time, for the data in the unprotected memory, the problem of data inconsistency may occur.

Here, the blogger will give you an example, and everyone will understand.

Assuming that the tickets are now 10000, thread A is now ready to perform the subtraction operation.

The subtraction operation corresponds to three steps:

  • Load tickets to cpu
  • cpu for calculation
  • Rewrite tickets back to memory

Assume that thread A has completed the first two steps, and when it is ready to perform the third step, thread A is cut off. At this time, thread A will store the number 9999 in the register, and when A is switched back next time, it will Continue to the third part: the operation of putting the data back into memory. However, because the memory of tickets is not protected, before thread A is switched back, the scheduler may have reduced the number of tickets to 5000. At this time, if thread A is switched back again, the tickets will be changed back to 9999 . This is the data inconsistency problem caused by parallel computing!

So we need to protect the memory of tickets! Since the thread is cut off, it is completely determined by the scheduler, so we have no way to control it. The only thing we can do is that when the thread is cut off, other threads cannot access tickets and must wait. After A is processed, other threads can process it!

In the operating system, tickets, which can be accessed and modified by multiple execution streams at the same time, are called critical resources.

Mutex

Why is the lock not added before the while, and the unlock is placed after the end of the while?

If this is the case, the logic of the entire ticket grabbing is completely serialized. What is the difference between this and no multi-threading? So when we lock, we must ensure the granularity of the lock, the smaller the better!

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <vector>
#include <time.h>
// To protect it with a lock
// pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; // pthread_mutex_t is a data type provided by the native thread library
int tickets = 10000;

#define THREAD_NUM 5 // Define the number of threads
struct ThreadData
{
public:
    ThreadData(const std::string n, pthread_mutex_t *pm)
        : tname(n), pmtx(pm) {}

public:
    std::string tname;
    pthread_mutex_t *pmtx;
};

void *getTickets(void *args)
{
    ThreadData *td = (ThreadData *)args;
    while (true)
    {
        pthread_mutex_lock(td->pmtx);
        if (tickets > 0) // The essence of judgment is also a kind of calculation
        {
            usleep(rand() % 1500 + 200); // Let the sleep time be random
            printf("%p: %d\\
", pthread_self(), tickets);
            tickets--;
            pthread_mutex_unlock(td->pmtx);
        }
        else
        {
            // no more tickets
            pthread_mutex_unlock(td->pmtx);
            break;
        }
    }
    delete td;
}

int main()
{
    pthread_mutex_t mtx;
    pthread_mutex_init( &mtx, nullptr);
    srand((unsigned long)time(nullptr) ^ getpid() ^ 0x147);

    pthread_t t[THREAD_NUM];
    // The logic of multi-thread grabbing tickets
    for (int i = 0; i < 5; i ++ )
    {
        std::string name = "thread";
        name + = std::to_string(i + 1);
        ThreadData *td = new ThreadData(name, &mtx);
        pthread_create(t + i, nullptr, getTickets, (void *)td);
    }
    for (int i = 0; i <thREAD_NUM; i ++ )
    {
        pthread_join(t[i], nullptr);
    }
    pthread_mutex_destroy( &mtx);
    return 0;
}

After the lock is added, will the thread switch in the critical section?

Will it switch, will there be a problem? No!
Although it is switched, we are holding the lock to be switched!
Other execution flows want to execute this part of the code and apply for locks, so other execution flows will fail to apply for locks
Does locking mean serial execution?
Yes! Execution of critical section code must be serial
To access critical resources, each thread must apply for a lock. The premise is that each thread must first see the same lock & amp; & amp; to access it. Then, is the lock itself a shared critical resource? ? Who will guarantee the safety of the lock? So in order to ensure the safety of the lock, the application and release of the lock must be atomic!!!
How is it guaranteed? What exactly is a lock? How is a lock implemented?

The principle of lock

End

Seeing this, everyone should have a preliminary understanding of locks. However, this blog is just to let everyone see what a lock is. In real applications, locks are not used in this way. There is still a lot of complicated and in-depth knowledge about mutexes in Linux. As for Linux multi-threading, the most important thing I think is the "producer-consumer model". This is really important. In the next few blogs, bloggers will talk about this content! If you think these contents are helpful to you, don't forget to like, collect and forward!

The knowledge points of the article match the official knowledge files, and you can further learn relevant knowledge. CS introductory skill tree Introduction to LinuxFirst-knowledge of Linux28810 People are studying systematically