Linux system programming - (pthread) thread communication (mutex)

Posted by Boo-urns on Tue, 11 Jan 2022 15:08:51 +0100

This article introduces the thread synchronization and mutex mechanism under Linux - mutex lock. When multiple threads are concurrent, multiple consumers will get data. In this case, the data needs to be protected. For example, like the train ticket system and the bus ticket system, the total number of tickets is fixed, but there are many ticket buying terminals.

Mutex is used to protect a resource from being used by two or more threads at the same time.

Why lock it?
This is because multiple threads share the resources of the process. When you want to access the public interval (global variable), when a thread accesses it, you need to lock it to prevent other threads from accessing it, so as to realize the exclusive use of resources. Only one thread can master a mutex at a time, and only threads with locked status can operate on shared resources. If another thread wants to lock a locked mutex, the thread will hang until the locked thread releases the mutex.

1. Introduction to mutex

In programming, the concept of object mutex is introduced to ensure the integrity of shared data operation. Each object corresponds to a tag called "mutex", which is used to ensure that only one thread can access the object at any time.

A set of mutex functions specially used for thread mutual exclusion is defined under Linux system.

Mutex is a simple locking method to control access to shared resources. This mutex has only two states (lock and unlock). Mutex can be regarded as a global variable in a sense.

Conclusion: mutex can protect a resource and can only be used by one thread at the same time.

2. Mutex related functions

#include <pthread.h>
//Destroy mutex
int pthread_mutex_destroy(pthread_mutex_t *mutex); 
//Initialize mutex
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//Locking: blocking mode
int pthread_mutex_lock(pthread_mutex_t *mutex);
//Locking: non blocking mode
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//Unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex);

explain:  about Linux Semaphore under/Compile the read-write lock file,It needs to be specified in the compilation options-D_GNU_SOURCE
 Otherwise use gcc Compilation will appear
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP Undeclared(First use in this function) Such tips.
for example: $ gcc app.c -lpthread -D_GNU_SOURCE

2.1 initialize mutex

Header file
#include <pthread.h>
Define function
int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutex_attr_t* attr );
Function description
 This function initializes a mutex variable if the parameter attr by NULL,Mutex variable mutex Use default properties.

2.2 destroy mutex

Header file
#include <pthread.h>
Define function
int pthread_mutex_destroy ( pthread_mutex_t *mutex );
Function description
 This function is used to release the assigned parameters mutex Resources.
Return value
 The return value is 0 when the call is successful, otherwise a non-0 error code is returned.

2.3 locking

Header file
#include <pthread.h>
Define function
int pthread_mutex_lock( pthread_mutex_t *mutex );
Function description
 This function is used to lock mutex variables. If parameter mutex If the mutex is locked, the calling thread will be blocked until other threads pair mutex Unlock.
If the lock is successful, 0 will be returned.

2.4 attempt to lock - non blocking

Header file
#include <pthread.h>
Define function
int pthread_mutex_trylock( pthread_t *mutex );
Function description
 This function is used to lock mutex The specified mutex, but not blocking.
Return value
 If the mutex is locked, the call does not block the wait, but returns an error code.
If the lock is successful, 0 will be returned.

2.5 unlocking

Header file
#include <pthread.h>
Define function
int pthread_mutex_unlock( pthread_mutex_t *mutex );
Function description
 This function is used to unlock a mutex. If the current thread has parameters mutex The specified mutex. This call unlocks the mutex.
If the unlocking is successful, 0 will be returned.
explain: Multiple unlocking of the same lock has no superposition effect. If the lock is locked, multiple unlocking is only effective once.

3. Application model of mutex framework

pthread_mutex_t mutex;
void Thread 1(void)
{
	while(1)
	{
	     //Lock
		pthread_mutex_lock(&mutex);
		.....Body code......
		//Unlock
		pthread_mutex_unlock(&mutex);
	}
}
void Thread 2(void)
{
	while(1)
	{
	     //Lock
		pthread_mutex_lock(&mutex);
		.....Body code......
		//Unlock
		pthread_mutex_unlock(&mutex);
	}
}

int main(void)
{
	//Initialize mutex
	pthread_mutex_init(&mutex,NULL);
	.....Body code......
	//Destroy mutex
	pthread_mutex_destroy(&mutex);
}

4. Case code: lock protection for public functions

The following code is that two threads call a print function at the same time to print: "123" and "456" respectively.

void print(char *p)
{
	while(*p!='\0')
	{
		printf("%c",*p++);
		sleep(1);
	}
}

void *thread1_func(void *arg)
{
	print("123\n");
}

void *thread2_func(void *arg)
{
	print("456\n");
}

If not protected, the default print result is:

[wbyq@wbyq linux-share-dir]$ ./a.out   412536  

The expected result should be that 123 \ 456 are printed continuously. In this case, it can be locked for protection.

Example code for locking:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;

void print(char *p)
{
    while(*p!='\0')
    {
        printf("%c",*p++);
        sleep(1);
    }
}

/*
Thread worker function
*/
void *thread_work_func(void *dev)
{
    while(1)
    {
        //Lock
        pthread_mutex_lock(&mutex);
        print("123\n");
        //Unlock
        pthread_mutex_unlock(&mutex);
        usleep(1000*10);
    }
}

/*
Thread worker function
*/
void *thread_work_func2(void *dev)
{
    //Lock
    pthread_mutex_lock(&mutex);
    print("456\n");
    //Unlock
    pthread_mutex_unlock(&mutex);
    usleep(1000*10);
}

int main(int argc,char **argv)
{   
    //Initialize mutex
    pthread_mutex_init(&mutex,NULL);

    /*1. Create child thread 1*/
    pthread_t thread_id;
    if(pthread_create(&thread_id,NULL,thread_work_func,NULL)!=0)
    {
        printf("Child thread 1 creation failed.\n");
        return -1;
    }
    /*2. Create child thread 2*/
    pthread_t thread_id2;
    if(pthread_create(&thread_id2,NULL,thread_work_func2,NULL)!=0)
    {
        printf("Child thread 2 creation failed.\n");
        return -1;
    }

    /*3. Introduction to waiting thread*/
    pthread_join(thread_id,NULL);
    pthread_join(thread_id2,NULL);

    //Destroy mutex
    pthread_mutex_destroy(&mutex);
    return 0;
}

5. Case code: simulate the train ticket sales system (protect the same global variable)

The following code simulates a train ticket sales system. If it is not locked here, negative tickets may be sold.

#include <stdio.h>
#include <pthread.h>
int cnt = 121; //Train tickets, public resources (global)
void* pthread1(void* args)
{
	while(ticketcount > 0)
	{
		printf("window A Start selling tickets,Tickets are:%d\n",cnt);
		sleep(2);
		cnt--;
		printf("window A End of ticket sales,The last ticket is:%d\n",cnt);
	}
}

void* pthread2(void* args)
{
	while(cnt > 0)
	{
		printf("window B Start selling tickets,The ticket is:%d\n",cnt);
		sleep(2);
		cnt--;
		printf("window B End of ticket sales,The last ticket is:%d\n",cnt);
	}
}
int main()
{
	pthread_t pthid1 = 0;
	pthread_t pthid2 = 0;
	pthread_create(&pthid1,NULL,pthread1,NULL);
	pthread_create(&pthid2,NULL,pthread2,NULL);
	pthread_join(pthid1,NULL);
	pthread_join(pthid2,NULL);
	return 0;
}

Train ticketing system after locking

#include <stdio.h>
#include <pthread.h>
int cnt = 121;
pthread_mutex_t lock;
void* pthread1(void* args)
{
	while(1)
	{
		pthread_mutex_lock(&lock); //Because you want to access the global shared variables, you need to lock them
		if(cnt > 0) //If there are tickets
		{
			printf("window A Start selling tickets,The ticket is:%d\n",cnt);
			sleep(2);
			cnt--;
			printf("window A End of ticket sales,The last ticket is:%d\n",cnt);
		}
		else
		{
			pthread_mutex_unlock(&lock);
			pthread_exit(NULL);
		}
		pthread_mutex_unlock(&lock);
		sleep(1); //Put it outside the lock so that the other has time to lock it
	}
}
void* pthread2(void* args)
{
	while(1)
	{
		pthread_mutex_lock(&lock);
		if(cnt>0)
		{
			printf("window B Start selling tickets--The ticket is:%d\n",cnt);
			sleep(2);
			cnt--;
			printf("window B End of ticket sales,The last ticket is:%d\n",cnt);
		}
		else
		{
			pthread_mutex_unlock(&lock);
			pthread_exit(NULL);
		}
		pthread_mutex_unlock(&lock);
		sleep(1);
	}
}
int main()
{
	pthread_t pthid1 = 0;
	pthread_t pthid2 = 0;
    //Initialization lock
	pthread_mutex_init(&lock,NULL); 
    //Create thread
	pthread_create(&pthid1,NULL,pthread1,NULL);
	pthread_create(&pthid2,NULL,pthread2,NULL);
     //Wait for the thread to end
	pthread_join(pthid1,NULL);
	pthread_join(pthid2,NULL);
     //Destroy lock
	pthread_mutex_destroy(&lock);
	return 0;
}

Topics: Linux Operation & Maintenance server