死锁概述

死锁(Deadlock):是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种状态时,若无外力作用,它们都将无法再向前推进。

资源问题

  • 可重用性资源和消耗性资源:

    • 可重用性资源:
      • 是一种可供用户重复使用多次的资源。每一个可重用性资源只能分配给一个进程使用,不允许多个进程共享。
      • 资源的单元数目是相对固定的,在运行期间既不能创建也不能删除它
    • 消耗性资源:
      • 在进程运行期间,由进程动态地创建和消耗。
      • 资源的单元数目在进程运行期间可以不断变化的。进程可以请求若干个可消耗性资源单元。
      • 可消耗性资源通常由生产者创建,消费者消耗。
  • 可抢占性资源和不可抢占性资源。

    • 可抢占性资源:
      • 某进程获得该资源后,该资源可以再被其他进程或系统抢占。
      • 不会引起死锁。
    • 不可抢占性资源是指资源一旦被分配给进程,只能在进程用完后自行释放。

### 计算机中的死锁

  • 竞争不可抢占性资源引起死锁。

    • 两进程分别保持一个临界资源,而又分别因请求对方所保持的资源被阻塞。
  • 竞争可消耗性资源引起死锁。

    • 一进程需接受到对方发送的消息a后才能发送消息b,而另一进程需接受到对方发送的消息b后才能发送消息a。
  • 进程推进顺序不当引起死锁。

    • 同样是请求和保持原因。

死锁的定义:

如果一组进程中的每一个进程都在等待仅由该组进程中的其它进程才能引发的时间,那么该组进程是死锁的。

产生死锁的必要条件

互斥条件:指进程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其他进程请求该资源,则请求者只能等待,直至占有该资源的进程用毕释放。

请求和保持条件:指进程已经保持了至少一个资源,但又提出了 新的资源请求(该资源又被其他进程占有,此时请求进程阻塞,但又对自己已获得的其他资源保持不放)。

不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放

环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链。即进程集合{P0,P1,P2,…,Pn}中的P0正在等待一个P1占用的资源;P1正在等待一个P2占用的资源,……,Pn正在等待一个已被P0占用的资源。

死锁处理方法

预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件的一个或几个,来预防发生死锁。

避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁

检测死锁:通过系统所设置的检测机构,及时的检测出死锁的发生,并精确的确定与死锁有关的进程和资源;然后采取适当的措施,从系统中将已发生的死锁清除掉

解除死锁:当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源

预防死锁

由于互斥条件是非共享设备所必须的,不仅不能改变,还应加以保证,因此主要是破坏其它三个条件。

破坏“请求和保持”条件

第一种协议:

​ 规定所有进程在开始运行之前,都必须一次性地申请其在整个运行过程所需的全部资源。在整个运行期间便不会再提出资源要求,从而破坏了请求条件。

​ 在分配资源时,只要有一种资源不能满足某进程的要求,即使其它所需的各资源都空闲,也不分配给该进程,即在该进程的等待期间,它并未占有任何资源,因而也破坏了保持条件。

第二种协议:

​ 允许进程只获得运行初期所需的资源后,便开始运行。

​ 进程运行过程中再逐步释放已分配给自己的、且已用毕的全部资源,然后再去请求新的所需资源

破坏不可抢占条件

当一个已经保持了某些资源的进程,再提出新的资源请求而不能立即得到满足时,必须释放它已经保持了的所有资源,待以后需要时再重新申请。

这意味着某一进程已经占有的资源,在运行过程中会被暂时地释放掉,也可认为是被抢占了,从而破坏了“不可抢占”条件。

破坏 循环等待条件

系统将所有类型资源进行线性排队,并赋予不同的序号。规定每个进程必须按序号递增的顺序请求资源。

假如某进程已请求到一些序号较高的资源,后来它又想请求一个序号低的资源时,它必须先释放所有具有相同和更高序号的资源后,才能申请序号低的资源

在采用这种策略后不可能再出现环路,因而破坏了“循环等待”条件。

避免死锁

系统安全状态:

系统在进行资源分配之前,应先计算此次资源分配的安全性。

安全状态,是指系统能按某种进程顺序( P0, P1, P2, …, Pn )来为每个进程Pi分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成。如果系统无法找到这样一个安全序列,则称系统处于不安全状态

安全状态下不会产生死锁,但是不安全状态不一定产生死锁

利用银行家算法避免死锁

描述资源的数据结构

在系统中必须设置四种数据结构,分别用来描述系统中可利用的资源、所有进程对资源的最大需求、系统中的资源分配,以及所有进程还需要多少资源的情况。

(设系统中有m类资源,n个进程)

  • 可利用资源向量 Available:

    • 这是一个含有 m 个元素的数组,其中的每一个元素代表一类可利用的资源数目,其初始值是系统中所配置的该类全部可用资源的数目,其数值随该类资源的分配和回收而动态地改变。

    • 如果 Available[j]=K,则表示系统中现有 R j类资源K 个。

  • 最大需求矩阵 Max:

    • 这是一个 n×m 的矩阵,它定义了系统中 n 个进程中的每一个进程对 m 类资源的最大需求。
  • 分配矩阵 Allocation:

    • 这也是一个 n×m 的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。
    • 如果 Allocation[i,j]=K,则表示进程 i 当前已分得 R j类资源的数目为 K。
  • 需求矩阵 Need。这也是一个 n×m 的矩阵,用以表示每一个进程尚需的各类资源数。

    • 如果 Need[i,j]=K,则表示进程 i 还需要 R j类资源 K 个,方能完成其任务。

上述三个矩阵间存在下述关系:Need[i, j] = Max[i, j] - Allocation[i, j]

银行家算法:

设 Request[i] 是进程 P i 的请求向量,如果 Request[i][j]=K,表示进程 P i需要 K 个 R j 类型的资源。当 P i发出资源请求后,系统按下述步骤进行检查:

  1. 如果 Request[i][j]≤Need[i,j],便转向步骤(2);否则认为出错,因为它所需要的资源数已超过它所宣布的最大值
  2. 如果 Request[i][j]≤Available[j],便转向步骤(3);否则,表示尚无足够资源,Pi 须等待。
  3. 系统试探着把资源分配给进程 P i,并修改下面数据结构中的数值:
    • Available[j] = Available[j]-Request[i][j]; 更新可用资源数目
    • Allocation[i,j] = Allocation[i,j]+Request[i][j]; 更新分配给该进程的资源数
    • Need[i,j] = Need[i,j]-Request[i][j]; 更新该进程需要的资源数

安全性算法:

  1. 设置两个向量:
    • 工作向量 Work,它表示系统可提供给进程继续运行所需的各类资源数目,它含有 m个元素,在执行安全算法开始时,Work=Available。
    • Finish: 含n个元素的一维数组,表示系统是否有足够的资源分配给n个进程,使之运行完成。开始时先令Finish[i]:=false (i=1..n); 当有足够资源分配给进程i时,再令Finish[i]:=true。
  2. 从进程集合中找到一个能满足下述条件的进程:
    • Finish[i]=false;
    • Need[i,j]≤Work[j];
    • 若找到,执行步骤(3),否则,执行步骤(4)。
  3. 当进程 Pi获得资源后,可顺利执行,直至完成,并释放出分配给它的资源,故应执行:
    • Work[j] = Work[j]+Allocation[i,j];
    • Finish[i] = true;
    • go to step 2;
  4. 如果所有进程的 Finish[i]=true 都满足,则表示系统处于安全状态;否则,系统处于不安全状态。

死锁的检测和解除

  1. 当系统为进程分配资源时,若未采取任何限制性措施,则系统必须提供检测和解除死锁的手段,为此系统必须:
    • 保存有关资源的请求和分配信息;
    • 提供一种算法,以利用这些信息来检测系统是否已进入死锁状态。
  2. 资源分配图:

系统死锁可利用资源分配图来描述:

该图由表示进程的圆圈和表示一类资源的方框组成

其中方框中的一个点代表一个该类资源,请求边是由进程指向方框中的rj,而分配边则应始于方框中的一个点。如图所示。

image-20250429200304004
  1. 死锁定理:

在资源分配图中找出一个既不阻塞又非独立的进程结点Pi,在顺利的情况下运行完毕,释放其占有的全部资源。

由于释放了资源,这样能使其它被阻塞的进程获得资源继续运行。

在经过一系列简化后若能消去图中的所有的边,使所有进程结点都孤立,则称该图是可完全简化的,反之是不可完全简化的。

S 状态为死锁状态的充分条件是当且仅当 S 状态的资源分配图是不可完全简化的

  1. 死锁的解除:

剥夺资源:从其他进程剥夺足够数量的资源给死锁进程以解除死锁状态。

撤销进程:最简单的是让全部进程都死掉;温和一点的是按照某种顺序逐个撤销进程,直至有足够的资源可用,使死锁状态消除为止。

死锁避免的处理代码

使用银行家算法,C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#include <bits/stdc++.h>

using namespace std;

// 银行家算法
// max矩阵,各进程最大资源需求
// allocation矩阵,各进程已分配资源
// need矩阵,各进程剩余的需求
// available矩阵,系统当前可用资源

// 安全序列开始逐个查找哪个进程能够被分配,也就是进程i的每个资源j,有need[i][j] < available[j],才能被分配
// 进程执行顺序需满足:每个进程执行时,其 Need ≤ 当前 available,且执行后释放 Allocation 资源,增加 available。

// 资源分配过程就是 保证 请求的 <= need 不超最大需求, 请求 ≤ Available,当前有足够资源
// 安全满足后, avilable 减少请求量,allocation 增加请求量,need - allocation(需求-新分配的)
// 分配后进行安全检查,有安全序列才可以分配

const int PROCESS_COUNT = 5; // 进程数
const int RESOURCE_TYPE = 3; // 资源类型数

vector<vector<int>> Max = {
{7, 5, 3}, // P0
{3, 2, 2}, // P1
{9, 0, 2}, // P2
{2, 2, 2}, // P3
{4, 3, 3} // P4
};

vector<vector<int>> allocation = {
{0, 1, 0}, // P0已分配资源
{2, 0, 0}, // P1
{3, 0, 2}, // P2
{2, 1, 1}, // P3
{0, 0, 2} // P4
};

vector<int> available = {3, 3, 2}; // 初始可用资源,p0


// 计算need矩阵(max-allocation)
vector<vector<int>> need(5, vector<int>(3));

void calculate_need() {
for (int i = 0; i < PROCESS_COUNT; i++) {
for (int j = 0; j < RESOURCE_TYPE; j++) {
need[i][j] = Max[i][j] - allocation[i][j];
}
}
}


// 回溯法寻找所有安全序列
vector<vector<int>> safe_sequences;

void Safe_Check(vector<int> work, vector<bool> finish, vector<int> path) {
// 完整安全序列被找到
if (path.size() == PROCESS_COUNT) {
safe_sequences.push_back(path);
}

for (int i = 0; i < PROCESS_COUNT; i++) {
// 判断进程i的需求j能否被成功分配,小于等于当前可用资源 work
bool can_execute = true;
for (int j = 0; j < RESOURCE_TYPE; j++) {
if (need[i][j] > work[j]) {
can_execute = false;
break;
}
}

if (!finish[i] && can_execute) { // 有进程且需求可满足
// 模拟分配资源并回收(Work += Allocation[i])
vector<int> new_work = work;
// 完成后释放的是全部已分配资源(Allocation[i])
for (int j = 0; j < RESOURCE_TYPE; j++) {
new_work[j] += allocation[i][j];
}
// 标记进程完成
vector<bool> new_finish = finish;
new_finish[i] = true;
vector<int> new_path = path; // 复制旧路径
new_path.push_back(i); // 添加当前进程i
Safe_Check(new_work, new_finish, new_path); // 递归搜索
}
}
}


// 安全状态检测
pair<bool, vector<vector<int>>> check_safety() {
safe_sequences.clear();
// 可用的资源分配给
vector<int> work = available;
vector<bool> finish(PROCESS_COUNT, false);
Safe_Check(work, finish, {});
return {!safe_sequences.empty(), safe_sequences};
}


// 资源请求函数
bool handle_request(int pid, vector<int> request) {
calculate_need(); // 更新need矩阵

// 步骤1:检查请求是否超过最大需求
for(int j = 0; j < RESOURCE_TYPE; j++){
if(request[j] > need[pid][j]){
cout << "错误:P" << pid << "请求超过最大需求,出现死锁("
<< need[pid][0] << "," << need[pid][1] << "," << need[pid][2] << ")" << endl;
return false;
}
}

// 步骤2:检查可用资源是否足够
for (int j = 0; j < RESOURCE_TYPE; j++) {
if(request[j] > available[j]){
cout << "请求被拒绝:可用资源不足(当前"
<< available[0] << "," << available[1] << "," << available[2] << ")" << endl;
return false;
}
}

// 模拟分配

// 保存原始状态
vector<int> original_available = available;
vector<vector<int>> original_allocation = allocation;
vector<vector<int>> original_need = need;
// 分配临时变量
vector<int> new_available = available;
vector<vector<int>> new_allocation = allocation;
vector<vector<int>> new_need = need;
//分配
for (int j = 0; j < RESOURCE_TYPE; j++) {
new_available[j] -= request[j]; // 系统可用的资源 - 请求量
new_allocation[pid][j] += request[j]; // 该进程已分配的资源 + 请求量
new_need[pid][j] -= request[j]; // 需求资源 - 请求量
}


// 检查新状态安全性
available = new_available;
allocation = new_allocation;
need = new_need;
auto [is_safe, seqs] = check_safety();

// 恢复全局变量
available = original_available;
allocation = original_allocation;
need = original_need;

if (is_safe) {
// 应用分配
available = new_available;
allocation = new_allocation;
need = new_need;
cout << "请求批准,系统进入安全状态" << endl;
cout << "安全序列:";
for (auto seq : seqs) {
for (int p : seq) cout << "P" << p+1 << " ";
cout << "| ";
}
cout << endl;
return true;
} else {
cout << "请求拒绝:分配后系统不安全" << endl;
return false;
}
}


void algorithm_explanation() {
cout << "\n=== 银行家算法核心过程可视化 ===" << endl;

// T0时刻状态
cout << "\n1. T0时刻初始状态:" << endl;
cout << "可用资源 Available: " << available[0] << "," << available[1] << "," << available[2] << endl;
cout << "分配矩阵 Allocation:" << endl;
for (int i=0; i<5; i++)
cout << "P" << i+1 << ": " << allocation[i][0] << "," << allocation[i][1] << "," << allocation[i][2] << endl;
calculate_need();
cout << "需求矩阵 Need:" << endl;
for (int i=0; i<5; i++)
cout << "P" << i+1 << ": " << need[i][0] << "," << need[i][1] << "," << need[i][2] << endl;

// 安全检查过程
cout << "\n2. 安全检查逻辑:" << endl;
cout << "寻找满足Need[i] <= Work的进程(初始Work=Available)" << endl;
auto [is_safe, seqs] = check_safety();
cout << "找到" << seqs.size() << "个安全序列,例如:";
for (int p : seqs[0]) cout << "P" << p+1 << "->";
cout << "\b (释放资源后Work更新)" << endl;

// P1请求处理示例
cout << "\n3. P1请求(1,0,2)处理流程:" << endl;
cout << "请求前Available: " << available[0] << "," << available[1] << "," << available[2] << endl;
cout << "Need[P1] before: " << need[0][0] << "," << need[0][1] << "," << need[0][2] << endl;
handle_request(0, {1,0,2});
cout << "请求后Available: " << available[0] << "," << available[1] << "," << available[2] << endl;
cout << "Allocation[P1] after: " << allocation[0][0] << "," << allocation[0][1] << "," << allocation[0][2] << endl;
cout << "Need[P1] after: " << need[0][0] << "," << need[0][1] << "," << need[0][2] << endl;
}



int main() {
calculate_need(); // 初始化need矩阵

// T0时刻安全检查
cout << "=== T0时刻系统安全检查 ===" << endl;
auto [initial_safe, initial_seqs] = check_safety();
if (initial_safe) {
cout << "系统处于安全状态,安全序列:";
for (auto seq : initial_seqs) {
for (int p : seq) cout << "P" << p+1 << "->";
}
cout << "\b (共" << initial_seqs.size() << "种)" << endl;
} else {
cout << "系统不安全" << endl;
}

// 处理P1请求
cout << "\n=== 处理P1的资源请求(1,0,2) ===" << endl;
handle_request(0, {1,0,2});

algorithm_explanation();

return 0;
}