Synchronization and mutex
1. Basic concepts of deadlock 1.1
Deadlock: a deadlock refers to the state in which multiple threads (processes) concurrently executed in the system are permanently blocked because they cannot obtain the required resources.
Conditions for deadlock:
A. Exclusion mutex: indicates that resources can only be used by one task (thread or process) at any time. If there are other tasks requesting this resource at this time, the requester can only wait until the tasks occupying the resource release the resource.
B. cannot be preemptible: when a task has a certain resource, it cannot lose the ownership of the resource unless it releases it.
C. Hold and wait: The task already has at least one type of resource, and then waits for other resources to be available.
D. cyclic wait: When a deadlock occurs, there must be a task-a circular chain of resources, that is, a task set {P0, P1, P2 ,···, p0 in Pn} is waiting for resources occupied by P1; P1 is waiting for resources occupied by P2 ,......, Pn is waiting for resources occupied by P0.
When a deadlock occurs, all the four necessary conditions must be met. Therefore, to handle the deadlock, we must avoid and prevent the four conditions from being established at the same time, alternatively, if four conditions are set at the same time, any of the conditions is damaged. The following methods are used to handle deadlocks:
- Deadlock Prevention
- Avoid deadlocks
- Deadlock Detection
- Remove deadlock
1.2 prevent deadlocks
This method is used to set certain conditions for resource application to destroy one or more of the four conditions necessary to generate a deadlock to prevent deadlocks. Deadlock Prevention is an easy-to-implement method and has been widely used. However, due to the strict restrictions imposed, system resource utilization and system throughput may be reduced.
The following restrictions are generally used:
A. Remove the hold and wait condition: the task needs to apply for all the resources it needs at A time. It can continue to run only when all resources are available. Because the task is required to apply for all its resources at a time, there is no such situation of holding and waiting for it. However, the disadvantage is that it is difficult to predict the resources required by a task in the actual system, and even if the resources required by the task can be predicted, tasks do not need to use these resources during running, because the resources actually needed during running are determined by the Code path, this may result in occupying unused resources and resulting in waste. The more serious problem is that the use of this precaution implies a requirement that the resources required by the task must be released at the same time. This means that all resources can be released only when the task does not need any resources, which can cause a great waste of resources.
B. Eliminate the condition that cannot be preemptible: If a task's request for a new resource cannot be met, it should release the resources it has held. Then, when the task tries to apply for resources again, it should apply for previously held resources and newly required resources. Compared with the first condition, this condition does not require a task to apply for all the resources required at a time, but is applied as needed. However, this method also has serious deficiencies. Therefore, when a resource fails to be applied for, it will release all resources that have been held, when you try to apply for resources again, you need to apply for all resources required at this time. This requires you to track the required resources, and when you try again, resources that have been held before may become unavailable, which undoubtedly increases programming complexity.
C. Eliminate the cyclic waiting condition: This method requires a uniform number of resources. If a task already has resources numbered I, it can only apply for resources numbered greater than I. This eliminates the loop wait condition.
1.3 avoid deadlock
This method is also a pre-prevention policy, but it does not need to take various restrictions in advance to damage the four necessary conditions for deadlock, but in the process of dynamic resource allocation, some method is used to prevent the system from entering an insecure state, so as to avoid deadlocks. The most representative method to avoid deadlocks is the Banker algorithm proposed by Dijkstra E.W in 1968: The Banker algorithm is the most representative algorithm to avoid deadlocks. In the method of avoiding deadlocks, tasks are allowed to dynamically apply for resources. However, before resource allocation, the system should calculate the security of the allocated resources. If the allocation does not cause the system to enter an insecure state, allocated. Otherwise, wait. A security sequence refers to a task sequence {P1 ,..., Pn} is safe, that is, for each task Pi (1 ≤ I ≤ n ), the amount of resources required for subsequent tasks does not exceed the total amount of resources currently occupied by the system and all processes Pj (j <I. Security Status and unsafe status:
- Security Status: if there is a security sequence P1,… consisting of all processes in the system ,..., Pn, the system is in a safe state. There must be no deadlocks in the security status.
- Insecure status: no security sequence exists. The insecure status may not necessarily cause deadlocks.
To use this policy, each task needs to estimate the maximum resource required by it, and then the system uses this information to create a resource demand table for use. However, this estimation is usually difficult. In addition, because the estimated amount of resources is the largest, the actual running of a task does not necessarily actually use the estimated maximum amount of resources, therefore, using this estimation to avoid deadlocks may lead to unnecessary blocking.
1. 4. detect deadlocks
Deadlock Detection does not have to take any restrictive measures in advance. It is only used to send a life-and-death lock during operation. Accurately determine the tasks and resources related to deadlocks, and then take appropriate measures to clear deadlocks from the system.
1.4.1 single-instance resource Deadlock Detection
A single instance has only one resource, so it can only be applied for by one task.
1.4.2 multi-instance resource Deadlock Detection
The so-called multi-instance resource means that there are multiple resources, so it may be used by multiple tasks at the same time.
1.5 unlock the deadlock
It is generally used together with Deadlock Detection. A common implementation method is to cancel or suspend some tasks to recycle some resources, and then assign these resources to the blocked tasks so that they are ready to continue running. Deadlock Detection and removal measures may make the system obtain better resource utilization and throughput, but the implementation is also the most difficult.
These policies are generally implemented and supported by the system. There are also some principles in the compilation of applications for reference:
A. Do not hold locks in long-time operations (such as I/O) that may adversely affect performance.
B. Do not hold the lock when calling functions outside the module and possibly re-entering the module.
C. In general, coarse-grained locks are preferred. If it is determined that coarse-grained locks have a great impact on performance, fine-grained locks are used.
D. When multiple locks are used, try to lock all tasks in the same order to avoid deadlocks.
2. Basic concepts of priority reversal 2.1
Priority reversal: Priority reversal refers to the status in which a high-priority task is blocked by waiting for resources held by a low-priority task. It has two basic manifestations:
A. low-priority T1 tasks hold resource R for running; high-priority task T2 starts to run. High-priority tasks need to use resource R, but cannot obtain the resource, thus blocking; the low-priority task T1 continues to run. It seems that the high-priority task has been preemptible by the low-priority task.
B. low-priority T1 tasks hold resource R for running; high-priority task T2 starts to run. High-priority tasks need to use resource R, but cannot obtain the resource, thus blocking; A low-priority task T1 is scheduled to run. At this time, task T3. whose priority is smaller than T2 but greater than T1 is started, it will seize task T1; in the end, it seems that task T3 has occupied task T2 with a higher priority. If scenario 1 is easy to see due to competitive resources, scenario 2 is more confusing, there is no competition between T2 and T3, and T3 actually runs before T1.
In most cases, priority reversal does not cause problems, because high-priority tasks only delay execution, but sometimes this is also a problem.
2.2 Solution
Because priority reversal is related to resource sharing and usage, you can add Resource Access Control protocols to solve this problem. The following three resource access control protocols can be used to solve the priority reversal problem:
A. Priority Inheritance Protocol
B. Ceiling Priority Protocol
C. Priority ceiling protocol
2.2.1 Priority Inheritance Protocol
Access control rules of this Protocol: If a task T1 is blocked because the resource R cannot be obtained and its priority is higher than that of the resource R owner T2, to raise the leader's priority of T2. Specific rules:
- If resource R is unavailable, the task T that requests the resource is blocked.
- If resource R is available, assign it to applicant task T
- When a task with a higher priority than task T applies for resource R, the priority of task T is increased to that of task T.
- When task T releases resource R, its priority is restored.
2.2.2 ceiling Priority Protocol
In the ceiling priority, all resources required by all tasks are known, and the priority of all tasks is also known. Each resource has a ceiling priority, which is equal to the highest priority of all tasks that need to use the resource. For example, the system has tasks T1 (Priority 4), T2 (Priority 6), T3 (Priority 8), T4 (Priority 10), resources R1, R2, R3, R4, task T1 requires resources R1 and R3, task T2 requires resource R2, and task T3 requires resources R2 and R4, task T4 uses resources R1, R3, and R4. the ceiling priorities of resources R1, R2, R3, and R4 are respectively 10, 8, 10, and 10. When using this protocol, the rules are as follows:
- If resource R is unavailable, the task T that requests the resource is blocked.
- If resource R is available, it is assigned to applicant task T, and the priority of task T is increased to the ceiling priority of the resource.
- When task T releases a resource with the highest ceiling priority, it re-adjusts its priority to ensure that the task runs with the highest ceiling priority among its resources at any time.
- When a task releases all resources in use, its priority is returned to the priority assigned to it by the system.
2.2.3 priority ceiling protocol
Similar to the ceiling Priority Protocol, all resources required by all tasks in the priority ceiling protocol are known, and the priority of all tasks is also known. Each resource has a ceiling priority, which is equal to the highest priority of all tasks that need to use the resource. For example, the system has tasks T1 (Priority 4), T2 (Priority 6), T3 (Priority 8), T4 (Priority 10), resources R1, R2, R3, R4, task T1 requires resources R1 and R3, task T2 requires resource R2, and task T3 requires resources R2 and R4, task T4 uses resources R1, R3, and R4. the ceiling priorities of resources R1, R2, R3, and R4 are respectively 10, 8, 10, and 10. The difference between the two lies in the rule that there is a current priority ceiling in the priority ceiling protocol, the current priority ceiling is equal to the highest ceiling priority of all resources currently in use, based on the current priority ceiling, the Protocol rules are as follows:
- If resource R is unavailable, the task T that requests the resource is blocked.
- If resource R is available and task T has a higher priority than the current priority ceiling, resource R will be allocated to T; otherwise, rule 3 will be viewed.
- If resource R is available and the ceiling priority of a resource held by task T is equal to that of the current priority, resource R is allocated to task T. Otherwise, task T is blocked.
- If task T has a higher priority than task T1 that blocks it, task T1 inherits the task T and runs the task with this priority, t1 will restore its original priority after it releases all resources whose ceiling priority is higher than T.
This Protocol combines the Priority Inheritance Protocol and the ceiling priority protocol to calculate the current ceiling priority of the currently used resource, but it is different from the running task priority; the task running priority is changed only when the task blocks a high-priority task, and this change is not simply set to the current priority change board, it inherits the priority of the blocked high-priority task.
3. Ensure that the program has only one running instance
One common requirement is that only one running instance of the application is running. This can be achieved through record locks. The implementation method is as follows:
A. Create A special file after the application is started. The file location and name are unique.
B. Try a record lock on the file and lock the entire file.
C. If the lock fails, an application instance is running and exits.
D. Continue execution
The record lock can achieve this purpose because, based on the record lock feature, no matter how the record lock owner process ends, the system automatically releases all record locks it holds-if the process does not release it by itself. As long as the instance cannot be locked, it indicates that a running instance is holding the lock.
Named semaphores have similar features (the system automatically disables all named semaphores opened when the process is terminated). Therefore, named semaphores can also be used for this purpose.