Linux -- thread synchronization (mutex lock, semaphore, read / write lock, spin lock, condition variable)

Posted by rotwyla98 on Sun, 06 Mar 2022 14:01:30 +0100

preface

When multiple control threads share the same memory, it is necessary to ensure that each thread sees a consistent data view. If the variables used by each thread are not read or modified by other threads, there is no consistency concept. Similarly, if the variables are read-only, there will be no consistency problem when multiple threads read the variables at the same time, However, when a thread can modify a variable and other threads can read or modify the variable, it is necessary to synchronize the threads to ensure that they will not access invalid values when accessing the stored contents of the variable.

As mentioned in the previous blog, for the self increment of this variable, when multiple threads access a piece of memory space at the same time, one thread has not completed its operation, and another thread has also carried out this operation, resulting in the result different from ours.
In today's blog, let's solve this problem:

The concept of thread synchronization

Thread synchronization means that when a thread is operating on a critical resource, other threads cannot operate on the resource until the thread completes the operation. That is, cooperate with the pace to make the threads run in a predetermined order. There are four methods of thread synchronization:

  • mutex
  • Semaphore
  • Conditional variable
  • Read write lock

mutex

concept
There are only two states of mutex, lock state and unlock state,
If a thread locks a mutex that is already in the locked state, the locking operation will be blocked until the thread in the locked state of the mutex is being unlocked.
Mutex interface

#include<pthread.h>

pthread_mutex_t  //Type of mutex

Initialize mutex

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

The second parameter is the lock attribute. Generally, NULL is passed in
Locking operation

int pthread_mutex_lock(pthread_mutex_t *mutex);

Try locking without blocking

int pthread_mutex_trylock(pthread_mutex_t *mutex)

Unlocking operation

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Destroy mutex

int pthread_mutex_destroy(pthread_mutex_t *mutex);

Solve previous problems:

After many tests, it is found that the value of wg has always been 5000, indicating that the mutex has played a role. But in fact, repeated lock and unlock operations also affect the execution efficiency of the program to a certain extent, but this is also a necessary sacrifice for the normal execution of the program.

use
Example: simulate two threads competing for a printer. Thread a uses the printer to output an A and outputs an a after use. The same is true for thread B, so that our output result will not appear abab/baba
How to make two threads operate on the same mutex: define the lock to the global

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<time.h>

#include<pthread.h>

pthread_mutex_t mutex;

void *threadFun(void *arg) //B thread
{
	int i = 0;
	for(;i < 5;i++)
	{
		pthread_mutex_lock(&mutex);
		printf("B");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);

		printf("B");
		fflush(stdout);
		pthread_mutex_unlock(&mutex);

		n = rand() % 3;
		sleep(n);
	}	
}

void threadMain() //A thread
{
	int i = 0;
	for(;i < 5;i++)
	{
		pthread_mutex_lock(&mutex);
		printf("A");
		fflush(stdout);
		int n = rand() % 3;
		sleep(n);

		printf("A");
		fflush(stdout);
		pthread_mutex_unlock(&mutex);

		n = rand() % 3;
		sleep(n);
	} 
}

int main()
{
	srand((unsigned int)time(NULL));

	pthread_mutex_init(&mutex,NULL);

	pthread_t id;
	int res = pthread_create(&id,NULL,threadFun,NULL);
	assert(res == 0);

	threadMain();

	pthread_join(id,NULL);
	pthread_mutex_destroy(&mutex);
	exit(0);
}

results of enforcement

Semaphore

concept
The principle of thread level semaphore is the same as that of process level semaphore. Semaphore is also a special counter. When the value is > 0, the number of critical resources is recorded. When = 0, it means that no critical resources are available. At this time, P operation is performed on semaphore, and the thread will be blocked.
Semaphore interface

#include<semaphore.h>

sem_t //Type of thread level semaphore

Initializes the semaphore and gives the initial value

int sem_init(sem_t *sem,int shared,int val);

Perform P operation on semaphore

int sem_wait(sem_t *sem);

Operation quantity V

int sem_post(sem_t *sem);

Destroy semaphore

int sem_destroy(sem_t *sem);

The difference between semaphores and mutexes

  • There are only two states of mutex, and the value of semaphore can be greater than 1
  • In a thread, the locking and unlocking of mutex must occur in pairs, and semaphores can be mixed between threads

Read write lock

concept
The read-write lock allows a higher parallelism based on the mutex lock. The read-write lock has three states:

  • Unlock status
    Any thread can lock successfully in any way.
  • Read lock status
    If a thread reads and locks the lock, it can succeed, but if a thread writes and locks, it will be blocked until all threads that read and lock perform the unlocking operation.
  • Write lock status
    As long as a thread has written and locked the lock, all locking operations will be blocked.

Interface of read-write lock

#include<pthread.h>

pthread_rwlock_t;  //Type of read / write lock

Initialize read / write lock

int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlock_t *attr);

Read lock operation

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

Write lock operation

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

Unlocking operation

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Destroy mutex

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);   

Spin lock

Similar to the mutex lock, but when the lock operation is blocked, the blocking method is different: the mutex lock is by sleeping the thread, and the spin lock is by busy waiting.
Spin locks are generally used in situations where the lock is held by other threads for a short time (it will be released soon), and the thread waiting for the lock does not want to be unscheduled during blocking, because this will bring some overhead.

Conditional variable

concept
Condition variables provide a place for multiple threads to meet. When condition variables are used with mutexes, threads are allowed to wait for specific conditions in a non competitive way.
The condition itself is protected by the mutex. The thread must lock the mutex before changing the condition state. Other threads will not notice the change before obtaining the mutex, because the condition can only be calculated after locking the mutex.
The condition variable provides an inter thread notification mechanism: wake up and wait when a shared data reaches a certain value
This thread that shares data.

Use of conditional variables

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // It will block and pass the lock int pthread in the locked state_ cond_ signal(pthread_cond_t *cond); // Wake up a single thread
int pthread_cond_broadcast(pthread_cond_t *cond); //Wake up all waiting threads
int pthread_cond_destroy(pthread_cond_t *cond);

Code example

#include <stdio.h> 
#include <stdlib.h> 
#include <assert.h> 
#include <string.h> 
#include <unistd.h>
 #include <pthread.h>
 
pthread_mutex_t mutex;
pthread_cond_t cond;
 
char buff[128] = {0};

void *fun(void *arg)
{
	int flg = *(int*)arg;
	while(1)
	{
		 pthread_mutex_lock(&mutex);
		 pthread_cond_wait(&cond, &mutex);  //  mutex must be a lock in a locked state, which adds the current thread to the queue of waiting condition variables in a mutually exclusive manner        
		 pthread_mutex_unlock(&mutex);
		
		if(strncmp(buff,"end",3) == 0)
		{
			break;
		}
		
 		printf("fun%d:%s\n",flag,buff);
 		memset(buff,0,128);
	}

	printf("fun over\n");
}
int main()
{
	 pthread_cond_init(&cond, NULL);
	 pthread_mutex_init(&mutex, NULL);
 
 	int flag1 = 1;
 	int flag2 = 2;
 	pthread_t id1, id2; 
 	pthread_create(&id1, NULL, fun, (void*)(&flag1));
 	pthread_create(&id2, NULL, fun, (void*)(&flag2));
 
 	  while(1)
 	  {       
 	  	 printf("input: ");
 	  	 fflush(stdout);
 	  	 fgets(buff, 127, stdin);
 	  	 
        if(strncmp(buff, "end", 3) == 0)
        {
	        pthread_mutex_lock(&mutex);
	        pthread_cond_broadcast(&cond);
	        pthread_mutex_unlock(&mutex);
	        break;
        }
        else
        {
        	pthread_mutex_lock(&mutex);
	        pthread_cond_signal(&cond);
	        pthread_mutex_unlock(&mutex);
        }
      }
      
     pthread_join(id1, NULL);
     pthread_join(id2, NULL);

	 pthread_mutex_destroy(&mutex);
	 pthread_cond_destroy(&cond);
 
  	 exit(0);
}

Topics: Linux