[inter thread communication] 1. Mutual exclusion and synchronization

Posted by Ixplodestuff8 on Mon, 03 Jan 2022 01:06:46 +0100

1, Overview of mutual exclusion and synchronization

In a multitasking operating system, multiple tasks running at the same time may need to access and use the same resource

There are dependencies between multiple tasks, and the operation of one task depends on another task

Synchronization and mutual exclusion are used to solve these two problems

Mutual exclusion means that a public resource can only be used by one process or thread at the same time, and multiple processes or threads cannot access public resources at the same time.

Synchronization means that two or more processes or counties coordinate and operate in a certain order during operation. Synchronization is to add order on the basis of mutual exclusion

The methods of process and thread synchronization and mutual exclusion in POSIX standard are semaphore and mutual exclusion lock

2, Mutex

1. Concept

mutex is a simple locking method to control access to shared resources. mutex has only two states, lock and unlock.

You need to apply for mutex before accessing the resource. If the resource is in the unlock status, mutex will apply for and lock it immediately

If the resource is in lock status, the applicant will be blocked by default and wait for mutex to unlock

Unlocking can only be performed by the locker

2. Initialize mutex

Pthread for mutex_ mutex_ T data type means that it must be initialized before using a mutex

Statically allocated mutex:

         pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

Dynamically allocated mutexes:

        pthread_mutex_t mutex;

        pthread_mutex_init(&mutex,NULL);

Pthread should be used when all threads using this mutex are no longer needed_ mutex_ Destroy destroy mutex

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t &mutex,pthread_mutexattr_t *attr);

Function: initialize mutex

Parameter: mutex: mutex variable

attr: the attribute of the mutex. NULL is the default attribute

Return value: success 0 failure non-0

3. Mutex lock

#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);

Function: lock the mutex. If it is locked, the caller will block it until the mutex is unlocked

Parameter: address of mutex

Return value: success 0 failure non-0

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);

Function: try to lock the mutex. If it has been locked, the locking failure will be returned immediately

Parameter: address of mutex

Return value: success 0 failure non-0

4. Mutex unlock

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);

Function: Unlock the specified mutex

Return value: 0 or non-0

5. Destroy mutex

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Function: destroy the specified mutex

Return value: 0 or non-0

6. Examples

The following example shows the problem that two threads read common resources at the same time without adding a mutex

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

int num = 2048;

void * thread1_func(void * arg)
{
	int readnum,subnum;
	printf("thread1 reading num......\n");
	sleep(1);
	readnum = num;
	printf("thread1 num = %d\n",readnum);
	
	subnum = 2000;
	sleep(1);
	if(readnum >= subnum){
		num = readnum - subnum;
		printf("thread1 success! num = %d\n",num);
	}else{
		printf("thread1 error! num = %d\n",num);
	}
	
}
void * thread2_func(void * arg)
{
	int readnum,subnum;
	printf("thread2 reading num......\n");
	sleep(1);
	readnum = num;
	printf("thread2 num = %d\n",readnum);
	
	sleep(1);
	subnum = 2000;
	if(readnum >= subnum){
		num = readnum - subnum;
		printf("thread2 success! num = %d\n",num);
	}else{
		printf("thread2 error! num = %d\n",num);
	}

}

int main(){
	printf("running main thread\n");
	pthread_t thread1,thread2;
	int a = 123;
	if(pthread_create(&thread1,NULL,thread1_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	if(pthread_create(&thread2,NULL,thread2_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);

	return 0;
}

The above example is modified by adding a mutex

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

int num = 2048;
//Because multiple threads use this mutex, the mutex is defined as global
//If it is defined in the main function, the mutex needs to be passed through parameters, which is troublesome
pthread_mutex_t mutex;

void * thread1_func(void * arg)
{
	int readnum,subnum;
	printf("thread1 reading num......\n");
	pthread_mutex_lock(&mutex);//Lock out before using public resources
	sleep(1);
	readnum = num;
	printf("thread1 num = %d\n",readnum);
	
	subnum = 2000;
	sleep(1);
	if(readnum >= subnum){
		num = readnum - subnum;
		printf("thread1 success! num = %d\n",num);
	}else{
		printf("thread1 error! num = %d\n",num);
	}
	pthread_mutex_unlock(&mutex);//Unlock after using public resources
	
}
void * thread2_func(void * arg)
{
	int readnum,subnum;
	printf("thread2 reading num......\n");
	pthread_mutex_lock(&mutex);//Lock out before using public resources
	sleep(1);
	readnum = num;
	printf("thread2 num = %d\n",readnum);
	
	sleep(1);
	subnum = 2000;
	if(readnum >= subnum){
		num = readnum - subnum;
		printf("thread2 success! num = %d\n",num);
	}else{
		printf("thread2 error! num = %d\n",num);
	}
	pthread_mutex_unlock(&mutex);//Unlock after using public resources
}

int main(){
	printf("running main thread\n");
	pthread_t thread1,thread2;
	pthread_mutex_init(&mutex,NULL);//Mutex initialization
	
	if(pthread_create(&thread1,NULL,thread1_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	if(pthread_create(&thread2,NULL,thread2_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);
	
	pthread_mutex_destroy(&mutex);//Destroy mutex
	return 0;
}

3, Semaphore (the following is based on the application of interprocess communication, and there are more other functions in interprocess communication)

1. General

Semaphores are widely used for synchronization and mutual exclusion between processes or threads. Semaphores are essentially a non negative integer counter, which is used to control access to public resources.

During programming, you can judge whether you have access to public resources according to the result of operating the semaphore value. When the semaphore value is greater than 0, you can access, otherwise it will be blocked.

Semaphore is also called PV operation. A p operation will reduce the sem value by one, and a V operation will increase the sem value by one. For P operation, if the semaphore sem value is less than or equal to 0, the P operation will be blocked. If the semaphore sem value is greater than 0, the P operation can be performed to reduce by one

Semaphores are mainly used for synchronization and mutual exclusion between processes or threads:

  1. If used for mutual exclusion, several processes (or threads) often set only one semaphore.
  2. If it is used for synchronous operation, multiple semaphores are often set and different initial values are arranged to realize the execution order between them.

Only one semaphore is required for mutual exclusion, regardless of several threads:

Program execution initialization semaphore (sem=1)

= = > thread 1 P operation (sem=0) = = > thread 1 execution = = > execution end V operation (sem=1)

= = > thread 2 P operation (sem=0) = = > thread 2 execution = = > execution end V operation (sem=1)

                                ==>... The execution order of the above threads is random

Multiple semaphores are required for synchronization:

Program execution = = "initialization semaphore (sem1 = 1, Sem2 = 0)

= = > thread 1P operation sem1, sem1 becomes 0 = = > thread 1 execution = = > execution end V operation sem2

= = > thread 2P operation sem2, sem2 becomes 0 = = > thread 2 execution = = > execution end V operation sem1

2. Semaphore initialization

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);

Function: create and initialize semaphores

Parameter: sem: address of semaphore

pshared: optional values:

0: signal sharing between processes

Greater than 0: signals are shared between processes

Value: initial value of semaphore

Return value: 0 - 1

 3. Semaphore P operation

#include <semaphore.h>
int sem_wait(sem_t *sem);

Function: reduce the value of semaphore by one. If the value of semaphore is less than or equal to 0, it will be blocked

Parameter: address of semaphore

Return value: 0 - 1

#include <semaphore.h>
int sem_try_trywait(sem_t *sem);

Function: reduce the semaphore value by one. If the semaphore value is less than or equal to 0, the semaphore operation fails and returns immediately

Return value: 0 - 1

4. Semaphore V operation

#include <semaphore.h>
int sem_post(sem_t *sem);

Function: add the semaphore value to send a signal to wake up the waiting thread

Parameter: address of semaphore

Return value: 0 - 1

 5. Gets the count value of the semaphore

#include <semaphore.h>
int sem_getvalue(sem_t *sem,it *sval);

Function: obtain the semaphore value represented by sem and save it in sval

Parameter: sem: address of semaphore

sval: address to save semaphore value

Return value: 0 - 1

6. Destroy semaphore

#include <semaphore.h>
int sem_destroy(sem_t *sem);

Function: destroy semaphore

Parameter: semaphore address

Return value: 0 - 1

7. Instance (synchronization)

Use two threads to output hello and world!, When semaphores are not used:

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

void printer(char *str){
	while(*str != '\0')
	{
		printf("%c",*str);
		fflush(stdout);
		str++;
		sleep(1);
	}
}

void * thread1_func(void * arg)
{
	char *str = "hello";
	printer(str);
}
void * thread2_func(void * arg)
{
	char *str = "world";
	printer(str);
}


int main(){
	printf("running main thread\n");
	pthread_t thread1,thread2;
	if(pthread_create(&thread1,NULL,thread1_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	if(pthread_create(&thread2,NULL,thread2_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);
	sleep(1);
	printf("\n");
	return 0;
}

After using semaphores:

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

sem_t sem1,sem2;

void printer(char *str){
	while(*str != '\0')
	{
		printf("%c",*str);
		fflush(stdout);
		str++;
		sleep(1);
	}
}

void * thread1_func(void * arg)
{
	char *str = "hello ";
	sem_wait(&sem1);
	printer(str);
	sem_post(&sem2);
}
void * thread2_func(void * arg)
{
	char *str = "world";
	sem_wait(&sem2);
	printer(str);
	sem_post(&sem1);
}


int main(){
	printf("running main thread\n");
	pthread_t thread1,thread2;
	sem_init(&sem1,0,1);
	sem_init(&sem2,0,0);
	if(pthread_create(&thread1,NULL,thread1_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	if(pthread_create(&thread2,NULL,thread2_func,NULL)!=0){
		perror("fail to pthread_create");
		exit(1);
	}
	pthread_join(thread1,NULL);
	pthread_join(thread2,NULL);
	sleep(1);
	printf("\n");
	sem_destroy(&sem1);
	sem_destroy(&sem2);
	return 0;
}

 

 

Topics: Linux