I want to synchronize our processes, because I'm a romantic programmer!

Hello, Hello, I'm grey ape, a program ape who can write bug s!

Today is 10.24 programmer's day πŸ™ŠπŸ™ŠπŸ™Š!

I'll write your name in the code πŸ–₯, Because I'm a romantic programmer πŸ™Š!

I'll plant you in my hair πŸ’»οΌŒ Because I'm a romantic programmer πŸ™Š!

I'm going to program the CV on your keyboard ⌨️, Because I'm a romantic programmer πŸ™Š!

I'll put you new in the object πŸ–±οΌŒ Because I'm a romantic programmer πŸ™Š!

I also want to synchronize our processes πŸ’ΎοΌŒ Because I'm a romantic programmer πŸ™Š!

So as a romantic programmer, how should we deeply realize process synchronization? Let's find out with the ape today πŸ˜‡ [the expression package in the article is a great reward]!

1 problem description

1.1 why process synchronization

Why is there a process synchronization mechanism in the operating system we use? At the beginning, our computer system was a single channel batch processing system, which means that only one program can be run at the same time. After this program is run, another program can be run. This will lead to low operation efficiency and insufficient utilization of resources in the system. This is also the main reason why the traditional operating system is inefficient in business processing, so how to solve this situation? This is also the reason for the emergence of multi-channel batch processing system.

Multiple programs are executed concurrently, which greatly improves the utilization of system resources. However, this system will cause some problems. For example, some resources, such as display and cpu, can only be used by one program at the same time, and multiple programs can not use the display at the same time. This is the mutual exclusion relationship. In addition, there is such A constraint relationship between some two processes: the output of program A is the input of program B, which is the synchronization relationship, In order to solve these problems, we introduce the process synchronization mechanism. By implementing the process synchronization mechanism, we can realize the synchronous access of multiple processes to resources and improve the efficiency of resource use.

1.2 method of process synchronization

For process synchronization, we often use semaphore mechanism. There are three commonly used semaphore mechanisms: integer semaphore, structural semaphore AND and semaphore. So what do these three semaphore mechanisms mean? Next, I will introduce the three semaphore mechanisms one by one.

1.2.1 integer semaphore

Integer semaphores are managed by an integer S, which represents the number of resources. We often operate resources in two ways, one is use and the other is release. They correspond to two functions: wait and signal

The wait function first judges whether the resource S can be used. If not, that is, S < = 0, the process will wait until S becomes a positive number before executing the wait method. Then, S is self subtracted once; signal means to release resources after using them, and S will increase once.

The specific implementation of Wait function and signal function is as follows:

wait(S){
    while(S<=0);
    S--;
}

signal(S){
    S++οΌ›
}

Disadvantages of integer semaphores:

From the above program, we can actually find a problem that when the semaphore S is less than 0, the program will always loop in the wait function. In this way, the process will be in a "busy" state. This is also the disadvantage of using integer semaphore mechanism. If you want to solve the problem of "busy waiting", you should let the program "give right waiting". That is, when resources cannot be used, the processor is released to avoid long-term occupation.

1.2.2 structure type semaphore

For the above problems, which process should we allow to access critical resources? At this time, the structure semaphore appears. We can use a linked list to solve this problem. A structure is used to describe resources. The structure is as follows:

typedef struct{
    int value;
    struct process_control_block *list;
}semaphore;

In this structure, there are two variables, one is value, which is used to record the number of resources, and the second is a pointer to the next process to use critical resources.

By using structural semaphores, the wait and signal functions are improved as follows:

wait(semaphore *S){
    S->value--;
    if(S->value<0)  block(S->list);
}

signal(semaphore *S){
    s->value++;
    if(S->value<=0)
      wakeup(S->list);
}

In the program, the initial value of S - > value represents the number of certain resources in the system, which can be called resource semaphore.

Note:

S> 0: indicates the number of resources available to concurrent processes.

When S < = 0: | S | indicates the number of self blocking processes due to the lack of the resource.

In the improved wait function, resources will be applied to the operating system first, and the number of resources will be reduced by one. If the resources are allocated, the block primitive is called to block the process.

In the signal function, resources will be released first, and the number of resources will be reduced by one, and then judgment will be made. If there are still waiting processes blocked in the semaphore linked list, the wakeup primitive will be called to wake them up.

1.2.3 AND semaphore

AND semaphores are used to allocate all the resources of a process in the whole running process to the process at one time, AND then release them at one time. As long as a process has not been allocated successfully, all other allocated resources will not be allocated to it. In other words, either allocate all the requested resources to the process or none at all. This has the advantage of avoiding deadlock.

When using this semaphore mechanism, the wait and signal functions are implemented as follows:

wait(S1,S2,...,Sn)
{
    while(true)
    {
        if(Si>=1&&...&&Sn>=1)
        {
            for(i=1;i<n;i++)
            Si--;
            break;
        }
        else
        {
            place the process in the waiting queue associated with.....
            //If the resource is not applied for, it will be hung up in the corresponding blocking queue
        }
    }
}

Ssignal(S1,S2,...Sn)
{
    while(true)
    {
        for(i=1;i<=n;i++)
        {
            Si++;
            remove all the process waiting in the queue associated with Si into the ready queue;
            //Go to the backup queue to wake up or remove processes blocked by Si resources
        }
    }
}

The above is a description of the functions and implementation principles of the three semaphore mechanisms.

1.3 use semaphore mechanism to solve problems

The problem to be solved by using semaphore mechanism is the mutual exclusion and synchronization of processes. So how are these two problems realized?

1.3.1 realize mutual exclusion

Suppose that two processes PA and PB are mutually exclusive, that is, they want to use the same critical resource. What should we do in this case? We can set a mutex semaphore with an initial value of 1, so that both processes can use the resource at the beginning. When process PA uses the resource, it will first call the wait function to reduce the number of resources by one. After the wait function is completed, it will make the semaphore mutex-1. In this way, the value of mutex is 0, and the other process PB cannot use the resource, After the process PA uses the resources, it will call the signal function to release the resources and make the semaphore mutex+1. At this time, mutex will become 1 again, and another process PB can use the critical resources.

semaphore mutex=1;
PA(){
    while(1){
        wait(mutex);
        Critical zone
        signal(mutex);
        Residual area
    }
}

PB(){
    while(1){
        wait(mutex);
        Critical zone
        signal(mutex);
        Residual area
    }
}

1.3.2 realizing precursor relationship

Assuming that P1 and P2 have a precursor relationship, and P2 can only execute after P1 is executed, how should this be implemented? At this time, a common semaphore S can be set, and the initial value is set to 0.

In process P1: S1;signal(S);

In process P2: wait(S);S2;

The above statement means to execute the statement of P1 first, and then release s, that is, S + +, so that P2 can execute after executing the wait function. Otherwise, if the signal function is not executed, s will be 0 and P2 cannot execute. In this way, the precursor relationship between P1 and P2 is realized.

Next, we will experiment with the problem of process synchronization and mutual exclusion described above. Verify the practical applicability of this method.

2 programming

2.1 demand analysis

The producer / consumer problem we often encounter in life is actually a typical process synchronization problem, in which producers produce resources and consumers consume resources. For example, the typical problem of buying steamed stuffed bun can be used to simulate the synchronization of processes. The execution between consumer and producer processes depends on the messages of another process. To represent the synchronization mechanism, you need to use the wait() / notify() method in Java to implement the synchronization mechanism. Since the package margin (number of resources) needs to be shared by all processes, only one process can access the buffer at any time. This needs to be implemented by using the synchronized code block in Java. The synchronized keyword is used to control the synchronization of multiple threads accessing resources. Its three usage methods are: modifying instance methods, modifying static methods Decorate code blocks.

If the method or code block is declared with synchronized, the lock of the object will protect the whole method or code block. To call this method or execute this code block, you must obtain the lock of this object. Moreover, only one thread object can execute protected code at any time.

2.2 algorithm design ideas

Taking the problem of buying steamed stuffed buns as an example, we now design two chefs (producers) and two customers (consumers). The maximum storage limit of steamed stuffed buns is 3 (buffer size). The initial value of steamed stuffed bun is 0. At this time, all buyer processes will enter the waiting state. All cook processes will keep making steamed stuffed buns before the remaining steamed stuffed bun does not exceed the size of the buffer, and wake up the buyer process that there are steamed stuffed buns to eat until the buffer is full to enter the waiting state. Each time the buyer process eats a steamed stuffed bun, it will wake up the cook process to continue making steamed stuffed buns. At the same time, because the package margin needs to be shared by all processes to ensure that only one process can access the buffer at any time, all process methods need to be declared with synchronized.

3 source code list

3.1 buffer common class

package com.common;

/**
 * Define buffer margins, methods performed by producers and consumers
 */
public class BaoZi {
    Integer count = 0;      //Record the total number of buns

    /**
     * Produce steamed stuffed bun (generate resources)
     *
     * @param name Chef name
     */
    public synchronized void makeBaoZi(String name) {
//        Judge whether the number of steamed stuffed buns is greater than 3 and reaches the upper limit
        while (count >= 3) {
            System.out.println(name + ": Steamed stuffed bun has reached the upper limit!");
            try {
                wait();     //The capacity of steamed stuffed bun reaches 3, exceeding the upper limit, and the producer enters the waiting
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count++;    //If you don't reach 3, make a steamed stuffed bun
        System.out.println(name + ": I made a steamed stuffed bun and there's still one left[" + count + "]One!");
        notifyAll();    //There are steamed stuffed bun resources to awaken consumers to buy steamed stuffed buns
    }

    /**
     * Buy steamed stuffed buns (use resources)
     *
     * @param name
     */
    public synchronized void buyBaoZi(String name) {
//        Judge whether the number of package sub resources is equal to 0,
        while (count == 0) {
            System.out.println(name + ": There are no steamed stuffed bun resources!");
            try {
                wait();     //There is no package resource, and the consumer process enters the waiting state
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;    //The number of steamed stuffed bun resources is reduced by one
        System.out.println(name + ": Bought a steamed stuffed bun, and the steamed stuffed bun resources remain[" + count + "]One!");
        notifyAll();    //If the number of steamed stuffed buns does not reach the upper limit, let the producer cook make steamed stuffed buns (wake up all processes waiting for the resource),
    }
}

3.2 producer process class

package com.producer;

import com.common.BaoZi;

/**
 * Producer process
 */
public class BaoZiPu extends Thread {
    private String name;    //Chef's name
    private BaoZi baoZi;    //Buffer package sub resource

    public BaoZiPu() {
    }

    public BaoZiPu(BaoZi baoZi, String name) {
        this.baoZi = baoZi;
        this.name = name;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(2000);     //Sleep for two seconds
                baoZi.makeBaoZi(name);  //Produce a steamed stuffed bun resource
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

3.3 consumer process

package com.consumer;

import com.common.BaoZi;

/**
 * Consumer process, consuming steamed stuffed bun resources
 */
public class Customer extends Thread {

    private String name;
    private BaoZi baoZi;

    public Customer() {
    }

    public Customer(BaoZi baoZi, String name) {
        this.baoZi = baoZi;
        this.name = name;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(2000); //Sleep for two seconds
                baoZi.buyBaoZi(name);   //Consume a package resource
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

3.4 testing

import com.common.BaoZi;
import com.consumer.Customer;
import com.producer.BaoZiPu;

public class TestMain {

    public static void main(String[] args) {
        System.out.println("==========Process synchronization test==========");
        BaoZi baoZi = new BaoZi();
        Thread rouBaoZi = new Thread(new BaoZiPu(baoZi, "Meat bun Chef"));
        Thread suBaoZi = new Thread(new BaoZiPu(baoZi, "Vegetarian steamed stuffed bun Chef"));
        Thread customerZS = new Thread(new Customer(baoZi, "Customer Zhang San"));
        Thread customerLS = new Thread(new Customer(baoZi, "Customer Li Si"));
        customerZS.start();
        rouBaoZi.start();
        suBaoZi.start();
        customerLS.start();

    }
}

4 test and analysis of operation results

4.1 test run results  

4.2 analysis of experimental results

According to the experimental test results, we can make such an analysis. When we create four processes, let the four processes share a cache resource (package) After that, Zhang San asked the customer to buy steamed stuffed buns first. As a result, there are 0 steamed stuffed buns at this time, so the process enters the waiting state. Then the vegetarian steamed stuffed bun chef and meat steamed bun chef access the resources and start to create steamed stuffed buns. After creating a steamed stuffed bun resource, Li Si also began to visit the steamed stuffed bun resources. All of them bought one steamed stuffed bun. There are 0 steamed stuffed buns left, and this time the meat steamed stuffed bun The cook's steamed stuffed bun is ready, so now there will be a steamed stuffed bun resource, but it will be bought by Zhang San in the waiting queue. At this time, the number of steamed stuffed bun resources does not reach the upper limit, so the steamed stuffed bun cook will continue to create steamed stuffed bun resources, and customers will continue to buy steamed stuffed bun until the number of steamed stuffed bun is 0, and customers will remind that there are no steamed stuffed buns.

5 Conclusion

The purpose of this experiment is to verify and test the problem of process synchronization under the operating system. Through experimental learning and code practice, I have a deeper understanding and understanding of the mechanism of inter process synchronization and mutual exclusion. Here, I simulate the producer / consumer problem through the cases of steamed stuffed bun shops selling steamed stuffed buns and buyers buying steamed stuffed buns.

There are two restrictions on the access of producers and consumers to the buffer. First, whether the steamed stuffed bun chef can produce steamed stuffed buns and put them in the buffer requires two conditions: first, the buffer needs to be idle, that is, whether the remaining steamed stuffed bun reaches the upper limit; second, obtain the lock of the current resource object and judge whether other producers or consumers are in the buffer. There are also two conditions for customers to purchase packages to access the buffer: the first is that there are resources in the buffer, that is, there are packages, and the second is to judge whether there are other producers or consumers in the buffer. This is realized by synchronizing the code block with the synchronized keyword.

Romantic programmer friend. Have you learned? Leave a message in the comment area and say what you think is the most romantic programmer love words!

I think it's good, remember 🀞🏻 One key three links 🀞🏻 Yo!

I'm the grey ape. I'll see you next time! πŸ’˜πŸ’˜πŸ’˜

Tags: Java JavaEE Operating System

Posted on Sun, 24 Oct 2021 01:18:26 -0400 by caspert_ghost