OO Unit 2 Blog Summary

OO Unit 2 Blog Summary

the first time

Design Strategy:

Direct use of producer and consumer models, no intermediate scheduler. The elevator Elevator directly acts as a consumer, reads the request, completes the request, and the producer class ElevatorReader is responsible for placing the request. Inspired by the Concurrent package, a thread-safe data class is set separately to store the queue and the "global end" flag. An instance of the above class is passed between the producer and the elevator for synchronous data transmission.

Elevator end strategy: If the current elevator is empty, the queue is empty, and the global end flag is set to end the thread. In the last discussion class of various teachers ’classes, some students put forward this method specifically as" delayed end ", that is, the external control thread only transmits the end signal. When the thread intercepts the signal, he will complete the necessary" end of life " Handle ", such as releasing the lock, completing tasks that have not been completed, and finally ending yourself, rather than directly ending from the outside. In the course discussion group, some students mentioned that the end time at the end is not safe, for example, the current task has not been completed. Using the above method can avoid this problem.

Scheduling Algorithm:

After consulting with some dalao s , everyone finally decided to use SSTF, and think that this algorithm can achieve better results when it is random enough. In order to solve the fatal and inefficient problem of SSTF for a certain data point, basically all students who use SSTF and pursue performance points have introduced a strategy of returning to pick people. If the elevator finds that there are people in the same direction as the main request, but it has fallen behind the elevator, calling the algorithm to calculate whether to return to the person or complete the task directly saves the floor. In most cases, returning to pick up people saves floors, so you can return to pick up people. This strategy continued through three assignments.

Analysis of code structure based on metrics:

as the picture shows:

As mentioned earlier, the first job is divided into four categories: main category, read thread, elevator thread, safe container

The timing diagram is as follows:

Basic process: The Main class starts the ElevatorReader and Elevator threads. Elevator blocks when the queue is empty, and stores it in the queue after the ElevatorReader reads the request, which is acquired and executed by Elevator. When reading into null, set the end flag of the thread-safe container and end ElevatorReader itself. When the elevator finds that the queue is completely empty, the instruction execution is completed, and when the end flag is set, it ends itself.

  • Code complexity:

    • Method complexity:

    • Class complexity:

    • Dependency matrix:

    As can be seen from the complexity of the method, the complexity of the haspeopletoget () method is slightly higher. This function is a function to determine whether there is a need to "receive people". "A total of four cases are classified, resulting in a large number of if-ese sentences. In a more ideal situation, the four situations can be performed separately as four functions, reducing complexity.

All the complexity of the class is controlled within a reasonable range.

Evaluation

This code has no bugs in public beta and mutual beta.

the second time

Adopting strategy:

Added elevator dispatcher, each elevator has its own queue. Added Person class to save information. The newly added scheduler is used to read the request from the producer and complete the allocation to the queue of each elevator. The main request of each elevator comes from its own queue, but its piggyback range includes all the people in the elevator's queue, and the return piggyback range includes only its own queue.

Scheduling Algorithm:

A single elevator follows the algorithm of the first job (guaranteeing local optimum), and the scheduler is mainly responsible for load balancing. In fact, the underlying idea of ​​many distributed systems is very similar to this, and it needs to balance load balance + local optimization. The author once planned to write an algorithm that prioritizes global optimization + perfect balance of local load balancing, but because the data interaction between threads is too complicated, unfortunately this version failed to pass the mid-test due to the strange RTLE reason that cannot be reproduced locally Therefore, the version of load balancing + local optimal is still submitted in the end. Manual testing after the release of the strong test data revealed that the version that failed the midtest was generally better than the version I submitted compared to the version I submitted.

Specific allocation principles:

  • The scheduler and reader continue the producer-consumer model, and the scheduler reads the request
  • The scheduler separately calculates the time increment t of the estimated end time of the elevator after assigning this task to each elevator
  • Choose the one with the smallest time increment as the tentative optimal solution.
  • Review, if there are available elevators, assign this task to empty elevators first. If there are elevators whose estimated end time differs by more than six seconds, priority load balancing will be assigned to the fastest elevator. If the first two are not satisfied, the smallest time increment is selected as the optimal solution.

Code structure analysis based on metrics

  • Code structure (UML class diagram)

  • Timing diagram

  • Code complexity:

    • Method complexity (only the first page after the screenshots are sorted by complexity, and the analysis is carried out with the complexity exceeding the standard)

    • Class complexity

As you can see, Elevator. The main running method is too complicated. This method continued to the third homework and became a major failure of this unit. I wrote all the main algorithm logic in the run method, except for some very specific subordinate logic, such as "get the main request", "offer", such as moving up and down, updating the internal state of the elevator, judging overload, judging the direction of movement And sleep reasonably for 400ms, output to reach the floor, etc., all integrated in one method. This is because during the iterative process, I was too lazy to open a new method, thinking about "can add a little is a little", which eventually led to a bloated structure.

As mentioned in the previous algorithm, the gettime () method is used to obtain the estimated completion time of the elevator. In order to implement this idea, I have implemented an elevator class that does not actually sleep, but simulates the operation of an elevator and returns the estimated time. The gettime () method is actually a transplant of the Elevator.run method, the reason has been clarified in the previous paragraph, and will not be repeated.

The haspeopletoget () method is used in homework 1, which has been analyzed.

The Scheduler.run () method is also a failure and will be analyzed in job 3.

From the perspective of class complexity, the class circle complexity of the Scheduler scheduler is significantly higher. This is due to the large number of judgments in my algorithm "Who is big, who is small, the largest, the smallest", full of if-else structure. These functions can be encapsulated.

Evaluation

This job did not find any bugs in public testing and mutual testing

In the mutual test, a classmate ’s code could not be ended under certain circumstances. Multiple data points can be reproduced locally stably, but I personally handed over 7 groups, and none of them hit the evaluation machine. There were other people in the same room. The test point of the same principle was hit (black face?).

the third time

Design Strategy

For this assignment, I almost completely completed the iteration based on Assignment 2, without adding many lines of code. Anyway, in operation 2, the queue of each elevator is independent, so no matter how many elevators are added, it is a matter of changing soup and not changing medicine. As for the splitting of requests, I set up a container in the scheduler. Whenever a request for transfer arrives, the scheduler distributes the first part of the request to the elevator and puts the rest in the queue. When the elevator ends one request at a time, the dispatcher is notified. The scheduler retrieves the queue, and if it finds that there are remaining transfer parts, it completely imitates the behavior of the "input read thread" and puts the remaining parts into the scheduler-producer interaction queue. The benefits of doing this are obvious: to maximize the equivalent conversion from never realized to realized functions . The only thing that needs to be newly added is the end condition of the elevator. I fear that the "remaining part of the request has not been completed" container is empty.

This kind of disadvantage is also obvious: the elevator thread directly calls the method in the dispatcher thread and modifies the internal data of the dispatch, and the dispatcher is still running at this time! ! Although I have tried my best to ensure that the modification is minimal, this method of writing is not recommended and may cause danger. The best way is at least to use the message mechanism mentioned in the seminar. The elevator is only responsible for delivering messages, and the specific logical processing is handled by the dispatcher's own thread (yes, I am lazy again, but I am based on Near-formal analysis proves that my operation does not cause thread safety problems).

Scheduling Algorithm

It is exactly the same as homework 2, the specific iteration method has been explained in the previous section

SOLID principle

  • Single responsibility principle:

    • The input class is responsible for reading the input and forwarding the request to the scheduler

    • Scheduler: Decide whether to split the request and split it, and assign the request to the elevator according to a specific algorithm

    • Elevator: responsible for reading its own queue, implementing specific operation algorithms and completing operations within it, its functions are more

    • Time estimation class: To estimate the remaining time of an elevator, during operation, the elevator class is required to pass almost all its variable values ​​to this class, so the coupling with the elevator class is very high, but it also belongs to "single responsibility".

  • Open and closed principle:

    • The framework structure is almost completely continued, so that the first unit and the refactoring of the chicken and chicken also experience the feeling of "iterative development" (of course, due to the limitations of the previous code architecture, some performance optimization of the third job cannot Carried out, lost one (100 million) points of performance points.
    • In the three iterations of the job, new classes have been added and independent, such as the PersonRequest class for storing personnel information, compared to the scheduler class for the first job. The interface between some classes has changed, but the logic has hardly changed. Specifically for the three core categories:
      • Elevator: From job 1 to job 2, the direct interaction with Scanner has become the interaction with the dispatcher. From job 2 to job 3, the function of notifying the dispatcher when leaving is added. But the overall structure continues.
      • Scheduler class: The ability to split requests and enter split requests as needed from job 2 to job 3 is a strictly incremental development without any line of code that has nothing to do with the new function
      • Input thread: Added support for elevator requests from job 2 to job 3. Since elevator requests and human requests use the same interface for abstraction, they are all sent to the same queue, and the modification is very small.
  • Richter's substitution principle: no inheritance is involved, I originally wanted to make a little inheritance between the time estimation class and the elevator class, but found it difficult to complete this abstraction, resulting in a large part of the duplicate code (time estimation class and the algorithm that the elevator itself runs Almost the same, the only difference is not really sleeping, regardless of thread safety), but because the sleep method does not allow rewriting, it is difficult to describe the two together. The author is still thinking about how to better solve this problem.

  • Interface separation principle: Only the Runnable interface and the Request interface, the latter abstracts ElevatorRequest and PersonRequest uniformly, achieves better abstraction, and reduces code complexity.

  • Dependency inversion principle: the elevator class and elevator time estimation class, and the scheduler class depend on each other, but this is a design strategy in itself, and there is no better way.

  • Function and performance balance:

    Hard

    Inspired by the "*** talk about governing the country: the bottom line thinking" (the name of the top leader cannot pass the cnblogs review) in the presentation of Mao's general lesson last semester, first write a minimum iteration to achieve the simplest version through the test Iterate in the direction of better performance, gradually evolving from the bottom line without destroying the overall framework, until the local optimal solution within the limits of the overall framework, the so-called "what can be done first". However, the so-called "unbroken overall framework" is still a relatively subjective concept after all. I have also mentioned that a version of the code has a very bad engineering structure for performance. Each thread passes a lot of information to each other, makes a lot of judgments, a lot of coupling, and a lot of check-then-act problems, resulting in this version. Failed to pass the test. Later, I implemented the "only necessary incremental development, as close as possible to the existing framework" method, originally thought that the performance of the third operation was improved, but the final result was a bit unexpected, which is also Once reflected the importance of the basic framework.

Metric-based code analysis

  • Code structure

  • Timing diagram

  • Code complexity

    • Method complexity

    • Class complexity

The gettime method, run method, and haspeopletoget method continue the previous code, analyzed in the previous chapter. The dealer method is a method of splitting requests. There are a lot of special judgments to determine how to split (if-else). Unless the algorithm is changed, this part is difficult to avoid.

In class complexity, a large piece of red highlights the gap in architectural capabilities between yourself and dalao s . On the one hand, its own algorithm is indeed more complicated, which leads to more judgments and more complicated processes. On the other hand, when writing programs, it is also full of brain correctness, and has not yet the ability to consciously maintain the code style. For example, the scheduler with the highest Ocavg, in fact, many methods can be disassembled / encapsulated into new classes / merged into other classes that are too simple (do nothing), but written in the scheduler following the wrong feeling, it is not only difficult to reuse, but also increases Complexity. Because of the lack of familiarity with multithreading at the beginning, a lot of processes were rigidly written in the run method. The run method of Scheduler and Elevator close to the Main was used, which has become a legacy of history. I glanced at some people's blogs and saw the design style of a dalao "all methods are controlled in thirty or forty lines", and I am ashamed. But overall, the over-complexity algorithm is significantly reduced compared to the first unit (take all opportunities to quickly boast about yourself)

Evaluation

This job did not detect any bugs in public beta and mutual beta

In the mutual test, the bugs of the two students tested were caused by the overload of the elevator. The reason is that the logic of the overload handling code is not well considered. But the strange thing is that the code of a classmate's local stable reproduction overload bug is still unable to judge the error in the evaluation machine.

Experience

  • Subjectively speaking, these three assignments are simpler than Unit1, and the performance of the first iteration of assignment 2 to assignment 3 is completed in a few hours, even shorter than the time I wrote this summary.

  • My own architectural design ability is still very bad.

  • My personal experience in avoiding deadlock is to avoid forming loops. In the iterative version of the unavoidable loop, there was no accident of overturning. The other versions are very smooth, leading to a lot of students complaining about deadlocks, CTLE, RTLE and other issues without any fluctuation in their hearts. Of course, the negative effect is that the multithreaded debugging capabilities are not fully trained.

  • Multithreaded development is really difficult, if you do not master the correct development skills, such as the message passing mechanism. Multi-threading and ancestor sacrifice are the same. You need to consider concurrently in the brain to simulate the possible situation of all threads. However, the human brain is single-threaded after all. When data coupling and logic business become more and more complicated, this kind of consideration The complexity increases exponentially, and it is difficult to think clearly in the end. One part of the author's Feng Ru Cup project requires 3D modeling, and one of the processes requires smooth operation. Some software will become a PPT in this operation card, open the task manager, and find that it does not use the graphics card to calculate. The CPU utilization rate is only less than 20%, showing the tragedy of one core and 11 core onlookers. With such a small elevator program, students are so deeply troubled by thread safety. How do those large and complex software achieve multi-thread concurrency safely? This question is still worthy of investigation.

  • The strategy for finding other people's bugs in the unit is mainly black box testing, and the data is mainly randomly generated. After reading the previous blog, I learned that there are few mutual testing bugs in this unit + a little lazy to change the evaluation machine. I did not manually construct the boundary data for testing. After seeing the results of the subsequent mutual testing, I felt that I lost 100 million points. I must measure this unit (flag up).

  • Thanks to the source code of the evaluation machine provided by the 1706 Senior (or Senior Sister) who actually didn't know the real name. The code style is very good and the scalability is very strong. At a glance, I know that the author is the big guy. Thanks to a good code structure, it will soon be able to change to this year's homework requirements.

Guess you like

Origin www.cnblogs.com/alangy/p/12722075.html
Recommended