Linux multithreaded programming | thread synchronization and mutual exclusion

Synchronization and mutual exclusion between threads

Because threads share process resources and address space, when operating on these resources, the synchronization and mutual exclusion of resource access between threads must be considered. The following mainly introduces two thread synchronization mechanisms in POSIX, namely mutex and semaphore. These two synchronization mechanisms can be implemented by calling each other. The single mutex is more suitable for the case where the resources available at the same time are the only one; the semaphore is more suitable for the case where there are multiple resources available at the same time.

1. Mutex thread control

1.1 Mutual exclusion lock programming instructions

Mutex is a simple way of locking to control atomic operations on shared resources. The mutex has only two states, namely locked and unlocked, and the mutex can be regarded as a global variable in a certain sense. At the same time, only one thread can master a certain mutex, and the thread with the locked state can operate on the shared resource process. If other threads want to lock an already locked mutex, the thread will be suspended until the locked thread releases the mutex.
The mutex lock mechanism should not include the following basic functions:

  • Mutex initialization: pthread_mutex_init()
  • Mutex lock: pthread_mutex_lock()
  • Mutex lock judgment: pthread_mutex_trylock()
  • Mutex unlock: pthread_mutex_unlock()
  • Eliminate the mutex lock: pthread_mutex_destory()

Mutex can be divided into fast mutexes, recursive mutexes and error detection mutexes. The general default attribute is fast mutex

  • Fast mutex: the calling thread will block until the thread that owns the mutex is unlocked
  • Recursive mutex: able to return successfully, and increase the number of times the calling thread locks the mutex
  • Error detection mutex: a non-blocking version of fast mutex, it will return immediately and return an error message
1.2 Description of mutex function

pthread_mutex_init()

/*****pthread_mutex_init()*****/
函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
传 入 值:mutex 互斥锁
		 mutexattr -->PTHREAD_MUTEX_INITIALIZER 创建快速互斥锁
		 		   -->PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁
		 		   -->PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁
返 回 值:成功返回0;失败返回错误码

pthread_mutex_lock()
pthread_mutex_trylock()
pthread_mutex_unlock()
pthread_mutex_destory()

/*****pthread_mutex_****()*****/
函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex)
		 int pthread_mutex_trylock(pthread_mutex_t *mutex)
	     int pthread_mutex_unlock(pthread_mutex_t *mutex)
		 int pthread_mutex_destory(pthread_mutex_t *mutex)
传 入 值:mutex 互斥锁
返 回 值:成功返回0;失败返回-1

2. Semaphore thread control

2.1 Semaphore programming instructions

Semaphore is the PV atomic operation used in the operating system, and it is widely used in synchronization and mutual exclusion between processes or threads. The semaphore is essentially a non-negative positive counter, which is used to control access to public resources. The principle of
PV atomic operation is as follows: PV atomic operation is an operation on the integer counter semaphore sem. A P operation decrements sem by 1, and a V operation increases sem by 1. Processes (or threads) determine whether they have access to public resources based on the value of the semaphore. When the value of the semaphore sem>=0, the process (or thread) has access to public resources; on the contrary, when the value of the semaphore sem<0, the process (or thread) will block until the semaphore sem Value>=0.
PV atomic operations are mainly used in two typical cases of synchronization and mutual exclusion between processes or threads. If used for mutual exclusion, several processes (or threads) often only set one semaphore sem; if used for synchronization, multiple semaphores are often set, and different initial values ​​are arranged to achieve sequential execution between them.

2.2 Semaphore function description

Common functions for synchronization and mutual exclusion between threads:

  • Create semaphore and initialize: sem_init()
  • P operation, when the semaphore>0, the value of the semaphore is reduced by 1: sem_wait() and sem_trywait(), when the semaphore<0, the former will block the process, and the latter will return immediately
  • V operation, add 1 to the value of the semaphore, and send a signal to wake up the waiting process: sem_post()
  • Get the value of the semaphore: sem_getvalule()
  • Delete semaphore: sem_destory()
/*****sem_init()*****/
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value)
传 入 值:sem 信号量指针
		 pshared 决定信号量能否在几个进程间共享,一般取0表示是当前进程的局部信号量 
		 value 信号量初始化值
返 回 值:成功返回0;失败返回-1

sem_wait ()
sem_trywait ()
sem_post ()
sem_getvalule ()
sem_destory ()

/*****sem_****()*****/
函数原型:int sem_wait(sem_t *sem)
		 int sem_trywait(sem_t *sem)
	     int sem_post(sem_t *sem)
		 int sem_getvalule(sem_t *sem)
		 int sem_destory(sem_t *sem)
传 入 值:sem 信号量指针
返 回 值:成功返回0;失败返回-1

3. Function examples

On the basis of the code of thread.c, the mutex lock function is added to realize the sequential execution of multiple threads that were originally independent and disordered

/*****thread_mutex.c*****/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_NUMBER 3		//线程数
#define REPEAT_NUMBER 5		//每个线程中的小任务数
#define DELAY_TIME_LEVELS 10.0  //小任务之间的最大时间间隔
pthread_mutex_t mutex;

void *thrd_func(void *arg){
    
    
	int thrd_num = (int)arg;
	int delay_time = 0,count = 0;
	int res;

	res = pthread_mutex_lock(&mutex);	//互斥锁上锁
	if(res){
    
    
		printf("Thread %d lock failed\n",thrd_num);
		pthread_exit(NULL);
	}
	printf("Thread %d is starting\n",thrd_num);
	for(count = 0;count < REPEAT_NUMBER;count++){
    
    
		delay_time = (int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX)) + 1;
		sleep(delay_time);
		printf("\tThread %d: job %d delay = %d\n",thrd_num,count,delay_time);
	}
	printf("Thread %d finished\n",thrd_num);
	pthread_exit(NULL);
}

int main(){
    
    
	pthread_t thread[THREAD_NUMBER];
	int no = 0,res;
	void * thrd_ret;

	srand(time(NULL));
	pthread_mutex_init(&mutex,NULL);	//互斥锁初始化
	for(no = 0;no < THREAD_NUMBER;no++){
    
    
		res = pthread_create(&thread[no],NULL,thrd_func,(void*)no);
		if(res != 0){
    
    
			printf("Create thread %d failed\n",no);
			exit(res);
		}
	}
	printf("Create thread sucess\nWaiting for threads to finish...");
	for(no = 0;no < THREAD_NUMBER;no++){
    
    
		res = pthread_join(thread[no],&thrd_ret);
		if(!res)
			printf("Thread %d joined\n",no);
		else
			printf("Thread %d join failed\n",no);	
		pthread_mutex_unlock(&mutex);	//互斥锁解锁	
	}
	pthread_mutex_destory(&mutex);
	return 0;
}

The running results are as follows, it can be seen that the running order between the 3 threads is the same as the order in which the threads are created

linux@linux-virtual-machine:~/andy/proc$ ./thread_mutex
Create threads sucess
Waiting for threads to finish...
Thread 0 is starting
		Thread 0: job 0 delay = 7
		Thread 0: job 1 delay = 7
		Thread 0: job 2 delay = 6
Thread 0 finished	
Thread 0 joined
Thread 1 is starting
		Thread 1: job 0 delay = 3
		Thread 1: job 1 delay = 5
		Thread 1: job 2 delay = 10
Thread 1 finished
Thread 1 joined
Thread 2 is starting		
		Thread 2: job 0 delay = 6
		Thread 2: job 1 delay = 10
		Thread 2: job 2 delay = 8
Thread 2 finished
Thread 2 joined

Guess you like

Origin blog.csdn.net/Chuangke_Andy/article/details/108356000