Experiment 2 of operating system of Nanjing University of Posts and Telecommunications: mutual exclusion and synchronization of threads

Keywords: Back-end Operating System

Experimental principle and content

Critical area management based on mutual exclusion

  1. Using the editor gedit 2_1.c, create a new 2_1.c source file, create two threads to complete the ticket booking operation concurrently, and enter the following example code:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int ticketAmount = 2; //Global Variable
void* ticketAgent(void* arg){
    int t = ticketAmount;
    if (t > 0)
    {
        printf("One ticket sold!\n");
        t--;
    }else{
        printf("Ticket sold out!!\n");
    }
    ticketAmount = t;
    pthread_exit(0);
}
int main(int argc, char const *argv[])
{
pthread_t ticketAgent_tid[2];
//Create two threads through a loop based on the same thread function
    for (int i = 0; i < 2; ++i)
    {
        pthread_create(ticketAgent_tid+i, NULL, ticketAgent,NULL);
}
//The loop waits for all threads to end
    for (int i = 0; i < 2; ++i)
    {
        pthread_join(ticketAgent_tid[i],NULL);
    }
    printf("The left ticket is %d\n", ticketAmount);
    return 0;
}

The two threads share a global variable ticketAmount as the ticketing terminal. After the two threads run, print the remaining ticket amount in the main thread, compile and run this code for many times, observe the results, explain the reasons, and think about the range of the critical area in the thread function.

Multiple runs 2_ After 1. C, the result output with the remaining votes of 1 appears, because two threads access the critical area at the same time.

(2) Mutex access to critical area based on mutex lock

For the critical area in the thread function, the mutex lock in the pthread library is used to realize the mutex access of the critical area.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int ticketAmount = 2; //Global Variable
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //Global lock
void* ticketAgent(void* arg){
    pthread_mutex_lock(&lock);
    int t = ticketAmount;
    if (t > 0)
    {
        printf("One ticket sold!\n");
        t--;
    }else{
        printf("Ticket sold out!!\n");
    }
    ticketAmount = t;
    pthread_mutex_unlock(&lock);
    pthread_exit(0);
}
int main(int argc, char const *argv[])
{
    pthread_t ticketAgent_tid[2];
    for (int i = 0; i < 2; ++i)
    {
        pthread_create(ticketAgent_tid+i, NULL, ticketAgent,NULL);
    }
    for (int i = 0; i < 2; ++i)
    {
        pthread_join(ticketAgent_tid[i],NULL);
    }
    printf("The left ticket is %d\n", ticketAmount);
    return 0;
}

Code Description:

a. pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // Create a mutex

b. pthread_ mutex_ lock(&lock); // Lock

c. pthread_ mutex_ unlock(&lock); // Unlock

Through the mutex access of critical area based on mutex lock, the problem of two threads accessing critical area at the same time is effectively solved, and the results can be output correctly.

Semaphore based producer consumer synchronization

  1. Single buffer

The producer and consumer share a buffer, and use the method learned before to create two threads as the producer and consumer respectively. Create a new source code file 2_2.c, as follows:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>    // Semaphore header file
//Define synchronization semaphore variables (global variables)
sem_t empty;
sem_t full;
//Producer thread function
void* producerThd(void* arg){
    for(int i=0; i<10; i++){
        printf("**Producing one item.**\n");
        sem_wait(&empty);   //P(empty)
        printf("**PUTTING item %d to warehouse.**\n",i+1);
        sem_post(&full);     //V(full)
    }  
    pthread_exit(0); }
void* consumerThd(void* arg){
    for(int i=0; i<10; i++){
        sem_wait(&full);  //P(full)
        printf("##GETTING item %d from warehouse.##\n",i+1);
        sem_post(&empty);  //V(empty)
        printf("##Consuming the item.##\n");
    }  
    pthread_exit(0);
}
int main(int argc, char *argv[]) {
    pthread_t producer_tid, consumer_tid;
    //Semaphore initialization
    sem_init(&empty, 0, 1);
    sem_init(&full,  0, 0);  
    pthread_create(&producer_tid, NULL, producerThd, NULL);
    pthread_create(&consumer_tid, NULL, consumerThd, NULL);
    pthread_join(producer_tid, NULL);
    pthread_join(consumer_tid, NULL);
    //Destroy semaphore
    sem_destroy(&empty);
    sem_destroy(&full);
}

Use the following command to compile, generate an executable file, and run the program.

gcc 2_2.c -o 2_2 -pthread

./2_2

  1. Code Description:

To use semaphores, first include the header file < semaphore. H >

sem_t: Data type of semaphore

int sem_init(sem_t *sem, int pshared, unsigned int val);

The first parameter of the function is the semaphore pointer, the second parameter is the semaphore type (generally set to 0), and the third is the initial value of the semaphore. When the second parameter pshared is 0, all threads in the process are available. When it is not 0, different processes are available.

int sem_wait(sem_t *sem);

This function applies for a semaphore. If there is no available semaphore, wait. If there is an available semaphore, occupy a semaphore and subtract 1 from the value of the semaphore.

int sem_post(sem_t *sem);

This function releases a semaphore whose value is increased by 1.

int sem_destory(sem_t *sem);

This function destroys semaphores.

int sem_wait(sem_t *sem);

This function applies for a semaphore. If there is no available semaphore, wait. If there is an available semaphore, occupy a semaphore and subtract 1 from the value of the semaphore. Equivalent to P operation.

int sem_post(sem_t *sem);

This function releases a semaphore whose value is increased by 1. Equivalent to V operation.

Since the producer and consumer share a buffer, the two threads access and execute alternately.

  1. Multi buffer

When producers and consumers share multiple buffers, set Bank[10] as the warehouse, and there are 10 places to place goods. When the element is 0, it means empty, and when it is 1, it means there are goods. In addition to semaphore synchronization, it is also necessary to add mutually exclusive operation protection critical zone. Create a new source code file 2_3.c, code as follows:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
void printBank();  //This function outputs all the values in the buffer
int Bank[10]={0};
int in=0,out=0;  
sem_t empty;
sem_t full;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* producerThd(void* arg){
    for(int i=0; i<30; i++){       
        sem_wait(&empty);
        pthread_mutex_lock(&lock); //Critical zone start
        Bank[in] = 1;
        in = (in+1)%10;
        printBank();
      pthread_mutex_unlock(&lock);//End of critical zone
        sem_post(&full);
    }  
    pthread_exit(0);
}
void* consumerThd(void* arg){
    for(int i=0; i<30; i++){
        sem_wait(&full);
        pthread_mutex_lock(&lock); //Critical zone start
        Bank[out] = 0;
        out = (out+1)%10;
        printBank();
       pthread_mutex_unlock(&lock);//End of critical zone
        sem_post(&empty);
    }  
    pthread_exit(0);
}
/*This function outputs all the values in the buffer*/
void printBank(){
    printf("Bank:");
    for(int i=0; i<10; i++){
        printf("[%d]",Bank[i]);
        if(i==9) putchar('\n');
    }
}
int main(int argc, char *argv[]) {
    pthread_t producer_tid, consumer_tid;
    sem_init(&empty, 0, 10);
    sem_init(&full,  0, 0);
    pthread_create(&producer_tid, NULL, producerThd, NULL);
    pthread_create(&consumer_tid, NULL, consumerThd, NULL);
    pthread_join(producer_tid, NULL);
    pthread_join(consumer_tid, NULL);
    sem_destroy(&empty);
    sem_destroy(&full);
}

Compile and run the program. You can try to add sleep() function calls to the producer and consumer thread functions, adjust the execution speed and observe the execution results.

Calling sleep(1) in the thread function shows that the output value is different from the result of the sleep() function.

Production and consumption run in a time slice, the for loop output is completed, and the two threads execute alternately.

In the case of single core, the sleep (second) function thread tells the operating system that it does not need scheduling and does not allocate time slices to itself within the second seconds, so that the process is suspended and other processes get cpu resources.

Call the sleep() function so that when one process runs out of time slices, another process starts to execute, which will be different from the execution result without calling the sleep() function.

Solving apple orange problem based on semaphore

  1. Problem Description:

A plate that can hold N (where N is set to 3) fruits. Father only puts apples on the plate, mother only puts oranges, daughter only eats apples on the plate, and son only eats oranges. Three semaphores are used:

sem_t empty: the semaphore empty controls the number of fruits that can be placed on the plate, which is initially 3, because the plate is empty at the beginning, and the number of fruits that can be placed is 3.

sem_t apple ; The semaphore Apple controls the number of apples that the daughter can eat. The initial value is 0 because there are no apples on the plate at first.

sem_t orange; The semaphore orange controls the number of oranges the son can eat. The initial value is 0, because there are no oranges on the plate at first.

Mutex work_mutex can be consistent when it is printf output.

  1. Create a new source code file 2_4.c, the reference source code is as follows:
#include <stdio.h> 
#include <pthread.h> 
#include <semaphore.h> 
#include <unistd.h> 
sem_t empty; //Control the amount of fruit on the plate 
sem_t apple; //Control the number of apples 
sem_t orange; //Control the number of oranges 
pthread_mutex_t work_mutex=PTHREAD_MUTEX_INITIALIZER; //Declare mutex work_mutex 
int fruitCount = 0;//The amount of fruit on the plate 

void *thread_f(void *arg) //father thread 
{ 
while(1)
{ 
sem_wait(&empty); //Take up a plate space, and the number of fruits can be reduced by 1 
pthread_mutex_lock(&work_mutex); //Lock 
printf("Dad put in an apple!(Total current fruit on plate:%d)\n", ++fruitCount); 
sem_post(&apple); //An apple signal is released. The number of apples you can eat is increased by 1 
pthread_mutex_unlock(&work_mutex); //Unlock 
sleep(0.1); 
} 
} 

void * thread_m(void *arg) //mother thread 
{ 
while(1){ 
sem_wait(&empty); 
pthread_mutex_lock(&work_mutex); //Lock 
printf("Mom put in an orange!(Total current fruit on plate:%d)\n", ++fruitCount); 
sem_post(&orange); 
pthread_mutex_unlock(&work_mutex); //Unlock 
sleep(0.2); 
} 
} 
void * thread_s(void *arg) //son thread 
{ 
while(1){ 
sem_wait(&orange); //If you occupy an orange semaphore, the number of oranges you can eat will be reduced by 1 
pthread_mutex_lock(&work_mutex); //Lock 
printf("The son ate an orange!(Total current fruit on plate:%d)\n", --fruitCount); 
sem_post(&empty); //Eat an orange and free up space on a plate. Add 1 to the number of fruits 
pthread_mutex_unlock(&work_mutex); //Unlock 
sleep(0.2); 
} 
} 
void * thread_d(void *arg) //Daught thread 
{ 
while(1){ 
sem_wait(&apple); 
pthread_mutex_lock(&work_mutex); //Lock 
printf("The daughter ate an apple!(Total current fruit on plate:%d)\n", --fruitCount); 
sem_post(&empty); 
pthread_mutex_unlock(&work_mutex); //Unlock 
sleep(0.1); 
} 
} 
int main() 
{ 
pthread_t father; //Define thread 
pthread_t mother; 
pthread_t son; 
pthread_t daughter; 
sem_init(&empty, 0, 3); //Semaphore initialization 
sem_init(&apple, 0, 0); 
sem_init(&orange, 0, 0); 
pthread_create(&father,NULL, thread_f,NULL); //Create thread 
pthread_create(&mother,NULL, thread_m,NULL); 
pthread_create(&daughter,NULL, thread_d,NULL); 
pthread_create(&son,NULL, thread_s,NULL); 
sleep(1); 
sem_destroy(&empty); 
sem_destroy(&apple); 
sem_destroy(&orange); 
return 0; 
}

Compile and run the program many times, give the execution results and think about the execution.

  Use mutually exclusive semaphore work_ Mutex, pthread_ mutex_ lock(&work_mutex); // Lock, pthread_ mutex_ unlock(&work_mutex); // Unlocking realizes the mutually exclusive access of the four processes to the critical resource fruitCount.

Semaphore based critical zone management

Revision 2_1.c, try to use semaphores to solve the synchronization problem of ticket booking threads, and give the source code and implementation results.

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
int ticketAmount = 2;               //Global Variable 
sem_t mutex;                      //Mutually exclusive semaphore

void* ticketAgent(void* arg){
    sem_wait(&mutex);            //P(mutex)
    int t = ticketAmount;
    if (t > 0)
    {
        printf("One ticket sold!\n");
        t--;
    }else{
        printf("Ticket sold out!!\n");
    }
    ticketAmount = t;
    sem_post(&mutex);                   //V(mutex)
    pthread_exit(0);
}

int main(int argc, char const *argv[])
{
pthread_t ticketAgent_tid[2];
sem_init(&mutex,0,1);                      //The initialization semaphore is 1
//Create two threads through a loop based on the same thread function
    for (int i = 0; i < 2; ++i)
    {
        pthread_create(ticketAgent_tid+i, NULL, ticketAgent,NULL); 
}
//The loop waits for all threads to end
    for (int i = 0; i < 2; ++i)
    {
        pthread_join(ticketAgent_tid[i],NULL);
    }
    printf("The left ticket is %d\n", ticketAmount);
    sem_destroy(&mutex);                 //Undo semaphore
    return 0;
}

The operation results are as follows:

Solving driver conductor problem based on semaphore

Create a new source code file 2_5.c. realize the synchronization process of driver and conductor in classroom PPT through semaphore mechanism.

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
sem_t semid1,semid2;//Semid1 vehicle; Semid2 gate
void* driver()
{
int n=6;
 while(n)
 {
     n--;
     printf("\n");
     sem_wait(&semid1);
     printf("The driver started the car\n");
     printf("The driver drives normally\n");
     printf("The driver stops at the station\n");
     sem_post(&semid2);
 }
}
void* passenger()
{
int n=6;
 while(n)
 {
     n--;
     printf("The conductor closed the door\n");
     sem_post(&semid1);
     printf("The conductor sells tickets\n");
     sem_wait (&semid2); 
     printf("The conductor opened the door\n");
 }
}
int main()
{
 sem_init(&semid1,0,0);
 sem_init(&semid2,0,0);
 
 pthread_t tid1,tid2;
 
 pthread_create(&tid1,0,driver,0);
 pthread_create(&tid2,0,passenger,0);
 pthread_join(tid1,(void**)0);
 pthread_join(tid2,(void**)0);
return 0;
}

The operation results are as follows:

  Solve the driver conductor problem based on semaphores semid1 and semid2 (semid1 represents vehicle and semid2 represents door), and carry out SEM for the corresponding semaphores_ post(),sem_wait() implements program functions.

Posted by SalientAnimal on Sat, 27 Nov 2021 19:33:26 -0800