Linux-Thread synchronization and mutual exclusion

thread mutex

Thread Mutual Exclusion Condition

After understanding the basics of threads, we know that for threads, all threads share the address space of the process. File descriptors in the process, signal handling methods, and defined global variables can be seen by all threads. Threads complete the interaction between threads by sharing these resources.

We call these sharable resources critical resources. The code area where a program accesses critical resources is called a critical area.
Then when multiple threads operate critical resources concurrently, some problems will arise. Such as accessing a global variable. This will lead to data inconsistencies.

In order to prove the problem we are talking about, let's look at the following code (simulated ticket buying system)

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

int ticket=100;

void* route(  void *arg)
{
      char *id=(  char*)arg;
      while( 1)
      {

            if(ticket>0  )
            {
                  sleep(1);
                  printf(  "%s sells ticket %d\n",id,ticket);
                  ticket--;

            }
            else
            {

                  break;
            }
      }
}

int main(  )
{

    //创建4个线程,模拟四个用户买票
      pthread_t t1,t2,t3,t4;
      pthread_create(&t1,NULL,route,"thread_1");
      pthread_create(&t2,NULL,route,"thread_2");
      pthread_create(&t3,NULL,route,"thread_3");
      pthread_create(&t4,NULL,route,"thread_4");

      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
      pthread_join(t3,NULL);
      pthread_join(t4,NULL);
      pthread_mutex_destroy(&mutex);
      return 0;

}

write picture description here

In this example, the ticket is a critical resource. When several processes access it at the same time, the result is the result marked in the figure. Let's analyze the reasons below

  • Ticket is a non-atomic operation.
    The operation of subtracting 1 from the ticket corresponds to the assembly code, which is three statements.
1.load      将共享变量从内存加载道寄存器
2 updata    更新寄存器里的值,进行减1操作
3 store     将新值从寄存器加载到内存中

In these three-step operations, since it is not an atomic operation, the three-step operation will not be executed, and the execution will be restarted, which will cause data inconsistency. For example, when a thread 1 starts to operate the ticket variable (assuming that the initial value of the ticket is 100), it executes the second step and decrements the ticket by 1. At this time, due to the thread switch, thread 2 starts running and also accesses the ticket. At this point, thread 1 has just decremented the ticket by 1, changing the ticket to 99, and before it has time to put it back into memory, thread 2 enters this critical section. So thread 1 has to save 99 first. Give up the CPU to thread 2.

When thread 2 obtains CPU resources, it also decrements the ticke by one. For thread 2 and the following threads, when they access the ticket, they all perform a complete operation, that is, all three steps are performed, and then the resource is given up. Knowing that the ticket becomes 1,

When other threads have finished accessing the ticket, thread 1 is switched back, and he has just executed the second step, and the ticket in his eyes is 99. He will proceed to the third step, putting 99 back into memory.

However, now the ticket in memory is 1, (the result of the execution of threads 2, 3., 4). It is impossible to have both 1 and 99 for the same variable. So there is the data inconsistency problem mentioned above.

  • If the condition is true, the code can switch to other threads concurrently
  • During the long process of sleep() simulating business, many threads can enter the critical section

To solve these three problems, the following three things need to be done:

  • Code must have mutually exclusive behavior: when code enters a critical section line, no other threads are allowed to enter the critical section.

  • If multiple threads simultaneously request execution of code in a critical section, and no threads in the critical section are executing, then only one thread is allowed to enter the critical section.

  • If a thread is not executing in a critical section, then the thread cannot prevent other threads from entering the critical section.

To do these three points, in essence, you need a lock. The lock provided on Linux is called a mutex.
write picture description here

Mutex

There are two ways to initialize a mutex:

Method 1, static allocation:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

Method 2, dynamic allocation:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread mutexattr _t *restrict attr);

Parameters: mutex: the mutex to initialize

attr:NULL

destroy mutex

Destroying a mutex requires attention:

  • Mutexes initialized with PTHREAD_MUTEX_INITIALIZER do not need to be destroyed
  • Do not destroy a locked mutex that has already been destroyed, make sure that no thread will try to lock it later
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Mutex locking and unlocking

int pthread_mutex_lock(pthread mutex t *mutex); int pthread_mutex_unlock(pthread mutex t *mutex);

Return value: return 0 on success, error number on failure

When calling pthread_lock, the following situations may be encountered:

The mutex is in an unlocked state, the function will lock the mutex, and when the function call is returned successfully, other threads have already locked the mutex, or there are other threads that apply for the mutex at the same time, but there is no competition for the mutex , then the pthread lock call will block, waiting for the mutex to be unlocked.

Based on the above background knowledge, we are now improving the ticketing system just now. Add a mutex to him.


#1include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

int ticket=100;
pthread_mutex_t mutex;
void* route(  void *arg)
{
      char *id=(  char*)arg;
      while( 1)
      {
            pthread_mutex_lock(&mutex);

            if(ticket>0  )
            {
                  sleep(1);
                  printf(  "%s sells ticket %d\n",id,ticket);
                  ticket--;
                  pthread_mutex_unlock(&mutex);

            }
            else
            {
                  pthread_mutex_unlock(&mutex);
                  break;
            }
      }
}

int main(  )
{

      pthread_mutex_init(&mutex,NULL);
      pthread_t t1,t2,t3,t4;
      pthread_create(&t1,NULL,route,"thread_1");
      pthread_create(&t2,NULL,route,"thread_2");
      pthread_create(&t3,NULL,route,"thread_3");
      pthread_create(&t4,NULL,route,"thread_4");

      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
      pthread_join(t3,NULL);
      pthread_join(t4,NULL);
      pthread_mutex_destroy(&mutex);
      return 0;

}

write picture description here

Now, the data inconsistency problem caused by multi-threaded access to critical resources is solved.

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

//定义一个互斥锁和条件变量
pthread_mutex_t mutex;

pthread_cond_t cond;
//线程1的执行任务
void* r1(  void *arg)
{
      while(1)
      {
          //等待被唤醒后执行任务
            pthread_cond_wait(&cond,&mutex);
            printf("hello\n");
      }
}
//线程2的执行任务
void* r2(void *arg)
{
      while( 1)
      {
          //每隔1s唤醒线程1,提示可以执行任务了
            pthread_cond_signal(&cond);
            sleep(1);
      }
}

int main(  )
{

      //初始化互斥锁
      pthread_mutex_init(&mutex,NULL);
      //初始化条件变量
      pthread_cond_init(&cond,NULL);
      pthread_t t1,t2;
      //创建线程1,线程2
      pthread_create(&t1,NULL,r1,"thread_1");
      pthread_create(&t2,NULL,r2,"thread_2");
      //等待线程
      pthread_join(t1,NULL);
      pthread_join(t2,NULL);
      //销毁互斥锁和条件变量
      pthread_mutex_destroy(&mutex);
      pthread_cond_destroy(&cond);
      return 0;

}

write picture description here

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324613183&siteId=291194637