1, Purpose of the experiment:
1. Master basic synchronization and mutual exclusion algorithms and understand P and V operations.
2. Understand the producer consumer model and other typical synchronous and mutually exclusive models, such as philosopher dining, reader writer model, etc.
3. Understand the implementation method of process synchronization and mutual exclusion in LINUX, and master the use method of related functions.
4. Learn to use the basic synchronization objects in Windows and master the usage of relevant API s.
5. Understand the concurrent execution mechanism of multithreading in Windows to realize process synchronization and mutual exclusion.
2, Experimental environment:
LINUX environment
3, Experiment content:
PART 1 LINUX Environment
1, Introduction to relevant knowledge
1. Common thread functions
The use of pthread thread library uses the header file pthread. Com in the source code h. Add the - lpthread option when linking with gcc to link the thread library.
(1) pthread_create creates a thread
int pthread_create(pthread_t *thread, const pthread_attr_t attr,void(start_rtn)(void),void * arg);
Parameter 1: identifier of the generating thread
Parameter 2: the property of the generated thread, which is usually set to NULL
Parameter 3: function executed by the new thread
Parameter 4: parameters of the new thread function
Function return value: 0 if the thread is created successfully, otherwise - 1
(2) pthread_exit thread exits itself
(3) pthread_join other threads waiting for a thread to exit
int pthread_join( pthread_t thread , void **value_ptr);
Parameter 1: identifier of the waiting thread
Parameter 2: a user-defined pointer used to store the return value of the waiting thread
Function return value: 0 if the execution is successful, and the error number if it fails. This function is used if one thread wants to wait for the termination of another thread
(4) pthread_cancel other threads forcibly kill a thread
-
Mutex Usage
(1) Mutex operation related functions
pthread_mutex_init: mutex initialization
pthread_mutex_lock: mutex locking
pthread_mutex_unlock: release mutex
pthread_mutex_destroy: release mutex resource
(2) Methods of operating critical areas using mutexes:
Using the function pthread_mutex_lock
Critical zone operation
Using the function pthread_mutex_unlock release mutex -
Semaphore usage
(1) Semaphore correlation function
The data type of semaphore is sem_t
#include<semaphore.h>
Correlation function: sem_init ,sem_wait, sem_post, sem_destroy
int sem_init (sem_t *sem, int pshared, unsigned int value);
Parameter 1: semaphore to be initialized
Parameter 2: type of semaphore. If the value is 0, it indicates that it is a local semaphore of the current process. Otherwise, other processes can share the semaphore. LINUX threads generally do not support inter process shared semaphores, and this value is set to 0
Parameter 3: initial value of semaphore.
0 is returned when the call is successful, and - 1 is returned when it fails
int sem_wait(sem_t * sem);
Parameters: by sem_init calls the pointer sem of the initialized semaphore object to subtract 1 and wait for the semaphore. If the semaphore value is greater than 0, subtract 1 from the semaphore value and return immediately. If the value of the semaphore is 0, the thread is blocked. Equivalent to P operation.
0 is returned for success and - 1 is returned for failure
int sem_post(sem_t * sem);
Parameters: by SEM_ Pointer to the semaphore object initialized by the init call
Release the semaphore and increase the value of the semaphore by 1. Equivalent to V operation.
int sem_destroy(sem_t * sem);
Parameters: by SEM_ Pointer to semaphore object initialized by init call
Return all the resources you occupy. When cleaning up the semaphore, if there is still a thread waiting for it, the user will receive an error.
(2) Semaphore usage
Declare a semaphore sem_t my_sem;
Initialization semaphore SEM_ init(& my_sem,0,1);
sem_post and SEM_ The wait function is used together to achieve thread synchronization
Release semaphore int SEM_ destroy (& my_sem);
2, Experimental content
- Producer consumer problem
(1) The basic framework of the producer consumer problem is given below. The synchronous mutual exclusion control is not realized. Check the program operation results carefully and analyze the reasons.
#include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <time.h> #define N 4 / / number of consumers or producers #define M 20 / / number of buffers int in = 0; // Where the producer places the product int out = 0; // Where consumers take products char buff[M]; // buffer int producter_id = 0; //Producer id int consumer_id = 0; //Consumer id /* Print buffer */ void print() { int i; for(i = 0; i < M; i++) printf("%c ", buff[i]); printf("\n"); } /* Producer method */ void *producter() { int id = ++producter_id; while(1) { // The number of sleep can be used to adjust the speed of production and consumption sleep(2); char data; data=rand()%26+65; in = in % M; printf("Producer process%d stay%2d Location generation data%c: ", id,in,data); buff[in] = data; print(); ++in; } } /* Consumer approach */ void *consumer() { char data; int id = ++consumer_id; while(1) { // The number of sleep can be used to adjust the speed of production and consumption sleep(1); out = out % M; data=buff[out]; printf("Consumer process%d stay%2d Location consumption data%c: ",id, out,data); buff[out] = '*'; print(); ++out; } } int main() { pthread_t p[N]; pthread_t c[N]; int i; int ret[N]; for(i=0; i<M; i++) buff[i]='*'; //'*' indicates null, initialize buffer srand((int)time(NULL)); // Create N producer threads for (i = 0; i < N; i++) { ret[i] = pthread_create(&p[i], NULL,(void*)producter, (void *)(&i)); if(ret[i] != 0) { printf("producter %d creation failed \n", i); exit(1); } } //Create N consumer threads for(i = 0; i < N; i++) { ret[i] = pthread_create(&c[i], NULL, (void*)consumer, NULL); if(ret[i] != 0) { printf("consumer %d creation failed\n", i); exit(1); } } //Destroy thread for(i = 0; i < N; i++) { pthread_join(p[i],NULL); pthread_join(c[i],NULL); } exit(0); } ```bash
- Relationship analysis. The mutually exclusive access of producers and consumers to the buffer is a mutually exclusive relationship. At the same time, producers and consumers are a cooperative relationship. Only after producers produce, consumers can consume, and they are also a synchronous relationship.
- Organize ideas. It is relatively simple here. There are only two processes: producer and consumer. It happens that the two processes have a mutually exclusive relationship and a synchronous relationship. So what needs to be solved is the location of mutually exclusive and synchronous PV operations.
- Semaphore setting. As a mutually exclusive semaphore, mutex is used to control the mutually exclusive access buffer pool. The initial value of the mutually exclusive semaphore is 1; Semaphore full is used to record the number of "full" buffers in the current buffer pool. The initial value is 0. Semaphore empty is used to record the number of "empty" buffers in the current buffer pool. The initial value is n.
(2) The synchronous mutual exclusion control function is added to the code to enable the program to realize the producer consumer problem.
For the implementation method, refer to the algorithm on page 109 of the textbook. Tips:
Using two synchronous semaphores, sem_t empty_sem; // Synchronous semaphore, which prevents the producer from releasing products when it is full
sem_t full_sem; // Synchronize semaphores to prevent consumers from consuming when there is no product
Use a mutex semaphore, pthread_mutex_t mutex; // Mutex semaphore. Only one thread accesses the buffer at a time
#include <stdio.h> #include <pthread.h> #include <sched.h> #include <semaphore.h> #include <conio.h> #include <ctype.h> #include <signal.h> #include <iostream> #include<Windows.h> using namespace std; #pragma comment(lib,"pthreadVC2.lib") #define N 4 / / number of consumers or producers #define M 20 / / number of buffers int productin = 0; //Where the producer places the product int prochaseout = 0; //Where consumers take products int buff[M] = {0}; //The buffer is initialized to 0 and there is no product at the beginning. sem_t empty_sem; // Synchronous semaphore to prevent the producer from releasing products when it is full. sem_t full_sem; //Synchronize semaphores to prevent consumers from consuming when there is no product. pthread_mutex_t mutex; //Mutex semaphore. Only one thread accesses the buffer at a time. int product_id = 0; //Producer id int prochase_id = 0; //Consumer id void SignalExit(int signo) { printf("Program exit%d\n",signo); return; } void PrintProduction() { printf("The product queue at this time is::"); for(int i = 0; i < M; i++ ) { printf("%d ",buff[i]); } printf("\n\n"); } //Producer method void* Product(void* pramter) { int id = ++product_id; while(1) { Sleep(5000); //millisecond sem_wait(&empty_sem); //Subtract 1 from the semaphore pthread_mutex_lock(&mutex); productin = productin % M; printf("producer%d Put No. in the product queue%d Products\n\n",id,productin+1); buff[productin] = 1; PrintProduction(); ++productin; pthread_mutex_unlock(&mutex); //Release mutex object sem_post(&full_sem); //Add 1 to the value of the semaphore } } //Consumer approach void* Prochase( void* pramter ) { int id = ++prochase_id; while(1) { Sleep(7000); sem_wait(&full_sem); pthread_mutex_lock(&mutex); prochaseout = prochaseout % M; printf("consumer%d Remove the second item from the product queue%d Products\n\n",id,prochaseout+1); buff[prochaseout] = 0; PrintProduction(); ++prochaseout; pthread_mutex_unlock(&mutex); sem_post(&empty_sem); } } int main() { cout << "The number of producers and consumers is 5,The product buffer is 10,Producers produce a product every 2 seconds and consumers consume a product every 5 seconds" << endl << endl; pthread_t productid[N]; pthread_t prochaseid[N]; int ret[N]; //Initialization semaphore int seminit1 = sem_init(&empty_sem,0,M); int seminit2 = sem_init(&full_sem,0,0); if( seminit1 != 0 && seminit2 != 0 ) { printf("sem_init failed !!!\n"); return 0; } //Initialize mutex semaphores int mutexinit = pthread_mutex_init(&mutex,NULL); if( mutexinit != 0 ) { printf("pthread_mutex_init failed !!\n"); return 0; } //Create n producer threads for(int i = 0; i < N; i++ ) { ret[i] = pthread_create( &productid[i], NULL,Product,(void*)(&i) ); if( ret[i] != 0 ) { printf("producer%d Thread creation failed!\n",i); return 0; } } //Create n consumer threads for(int j = 0; j < N; j++ ) { ret[j] = pthread_create(&prochaseid[j],NULL,Prochase,NULL); if( ret[j] != 0 ) { printf("consumer%d Thread creation failed\n",j); return 0; } } for( int k = 0; k < N; k++ ) { printf("Destroy thread\n"); pthread_join(productid[k],NULL); pthread_join(prochaseid[k],NULL); } return 0; } ```bash
(3) Adjust the parameter values of sleep() function in producer method and consumer method, and analyze the running results. At least complete the following combination of parameter settings:
The parameter value of sleep() function in producer method and consumer method is 2;
The parameter value of sleep() function in producer method is 2, and the parameter value of sleep() function in consumer method is 4;
The parameter value of sleep() function in producer method is 4, and the parameter value of sleep() function in consumer method is 2;
2. Dining problem of philosophers
(1) The following code gives the basic framework of the philosopher's dining problem. It only realizes the mutual exclusion control of chopsticks, which will lead to deadlock. Understand the code and analyze the running results of the program.
Insert the code slice here #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <time.h> #define N 5 pthread_mutex_t chopstick[N];//Chopsticks semaphore //Philosopher thread function void* philosopher(void* arg){ int *i; i = (int *)arg;//Philosopher serial number for(;;){ //reflection printf("%d The philosopher is thinking......\n",*i); //sleep(rand()%3);// Sleep random time, no more than 3 seconds //Waiting for chopsticks pthread_mutex_lock(&chopstick[*i]); //sleep(rand()%2); // This statement can be added to facilitate the observation of deadlock pthread_mutex_lock(&chopstick[(*i+1)%N]); //eat printf("%d The philosopher is eating......\n",*i); //sleep(rand()%3);// Sleep random time, no more than 3 seconds //Put the chopsticks back pthread_mutex_unlock(&chopstick[*i]); pthread_mutex_unlock(&chopstick[(*i+1)%N]); } } int main(){ pthread_t id[N]; int i; for(i=0;i<N;i++) pthread_mutex_init(&chopstick[i],NULL); for(i=0;i<N;i++) { int *p; p=malloc(sizeof(int)); *p=i; pthread_create(&id[i],NULL,philosopher,(void*)p); } for(i=0;i<N;i++) pthread_join(id[i],NULL); } (2) The textbook gives three ways to solve the deadlock of philosophers' dining problem. Based on the above code, at least two solutions are realized. semaphore chopstick[5] = {1,1,1,1,1}; semaphore num_mutex = 4; while(wait(num_mutex)) { wait(chopstick[i]); wait(chopstick[(i+1)%5]); // eat() signal(chopstick[i]); signal(chopstick[(i+1)%5]); signal(num_mutex); } semaphore chopstick[5] = {1,1,1,1,1} do{ ··· // think() swait(chopstick[(i+1)%5],chopstick[i]); //eat() signal(chopstick[(i+1)%5],chopstick[i]); }while(true) semaphore chopstick[5]={1,1,1,1,1}; void philosopher(int i) { while(true) { think(); if(i%2 == 0) //Even philosophers, right first and then left. { wait (chopstick[(i + 1)%5]) ; wait (chopstick[i]) ; eat(); signal (chopstick[(i + 1)%5]) ; signal (chopstick[i]) ; } else //Odd philosophers, left first and then right. { wait (chopstick[i]) ; wait (chopstick[(i + 1)%5]) ; eat(); signal (chopstick[i]) ; signal (chopstick[(i + 1)%5]) ; } } }