银行家算法实现——找出所有安全序列

时间:2021-12-22 10:13:50

银行家算法实现——找出所有安全序列

一 .死锁概述

所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。——百度百科

通俗的说,A进程持有资源a,还需要资源b,B进程持有资源b,还需要资源a,在无其他外界条件下,A进程需要B进程资源,B进程需要A进程资源,谁也不肯放手,我们就说这时出现死锁。
解决死锁问题主要通过预防死锁,避免死锁,检测死锁,解除死锁四个方面。

二. 银行家算法

银行家算法是从避免死锁来解决死锁问题。

1. 数据结构

available[m]     //m种资源,每种资源可用数目
max[i][j]       //第i个进程第j种资源最大需求量
allocation[i][j]    //第i个进程第j种资源已经分配的量
need[i][j]       //第i个进程第j种资源还需要的量
由上可以看出 max[i][j] = allocation[i][j] + need[i][j]

2. 算法步骤

设requesti是Pi的请求向量
若request[j]>need[i][j],程序报错
若request[j]>available[i][j],系统资源数不够,进程陷入等待
系统尝试着把资源分配给进程i,修改如下数据
available[j] -= request[j]
allocation[i][j] += request[j]
need[i][j] -= request[j]
接下来运行安全性算法

设置工作向量work[m],表示系统目前的各类资源数,初始work=available
finish[n],表示该进程是否在安全性队列中,初始finish[i] = false
从进程集合中找出一个满足下列条件的进程:
finish[i] = false,need[i][j] <= work[j]
若找到,修改下列值:
finish[i] = true
work[i] += allocation[i][j]
继续寻找下一个满足的进程

若所有的finish[i] = true,表示系统处于安全性状态,之前提出的请求可以满足。

3. 程序实现

我们先看一下安全性算法的c++代码

void isSafe()
{
    int i, j, k;
    for(i=1; i<=n; i++)                        //安全性序列要选n个进程 
    {
        for(j=0; j<n; j++)                     //每次选择从n个进程里选一个 
        {
            bool pd = true;
            if(finish[j]){
                continue;
            }
            for(k=0; k<m; k++)                 //每次选择要判断m个资源
            {
                if(work[k] < need[j][k])       //若有一个资源不满足,则break,寻找下一个进程 
                {
                    pd = false;
                    break;
                }
            }
            if(pd){
                ans.push_back(j);
                finish[j] = true; 

                for(k=0; k<m; k++){            //选完j进程后,把已分给j进程的资源收回 
                    work[k] += allow[j][k];
                }
                break;
            } 
        }

        if(j >= n){
            break;          
        }
    }

    if(i > n){
        for(j=0; j<ans.size(); j++)
        {
            cout << "P" << ans[j] << ",";
        }
        cout << endl;
        sum++;
    }   
}

主体为三重循环,时间复杂度为O(n*n*m),第一层循环代表安全序列里要有n个,第二层循环代表每次要从n个进程里选出一个,第三层循环代表判断该进程是否可选(其m个资源是否满足要求),因为安全序列只要找到一个,且每个进程选定后,后一个进程不会对前一个进程产生影响,也就是不会产生回溯现象(为什么?)。

完整代码如下(为了便于测试,没有提示输入说明代码)

#include<cstring>
#include<vector>
#include<iostream>
using namespace std;
int resource[4] = {10, 5, 7};
int available[4] = {3, 3, 2};
int work[4]; 
int allow[6][4] = {{0, 1, 0}, {2, 0, 0}, {3, 0, 2}, {2, 1, 1}, {0, 0, 2}};
int need[6][4] = {{7, 4, 3}, {1, 2, 2}, {6, 0, 0}, {0, 1, 1}, {4, 3, 1}};
int maxx[6][4] = {{7, 5, 3}, {3, 2, 2}, {9, 0, 2}, {2, 2, 3}, {4, 3, 3}};

int n = 5;           //进程数
int m = 3;           //资源数

bool finish[6];
vector<int> ans;
int sum = 0;
void isSafe()
{
    int i, j, k;
    for(i=1; i<=n; i++)                        //安全性序列要选n个进程 
    {
        for(j=0; j<n; j++)                     //每次选择从n个进程里选一个 
        {
            bool pd = true;
            if(finish[j]){
                continue;
            }
            for(k=0; k<m; k++)                 //每次选择要判断m个资源
            {
                if(work[k] < need[j][k])       //若有一个资源不满足,则break,寻找下一个进程 
                {
                    pd = false;
                    break;
                }
            }
            if(pd){
                ans.push_back(j);
                finish[j] = true; 

                for(k=0; k<m; k++){            //选完j进程后,把已分给j进程的资源收回 
                    work[k] += allow[j][k];
                }
                break;
            } 
        }

        if(j >= n){
            break;          
        }
    }

    if(i > n){
        for(j=0; j<ans.size(); j++)
        {
            cout << "P" << ans[j] << ",";
        }
        cout << endl;
        sum++;
    }   
}

void request(int p, int req[])
{
    int i;
    bool pd = true;
    for(i=0; i<m; i++){
        if(need[p][i] < req[i])
        {
            pd = false;
            break;
        }
    }
    if(!pd){
        cout << "请求资源数错误\n";
        return;
    }

    pd = true;
    for(i=0; i<m; i++){
        if(available[i] < req[i])
        {
            pd = false;
            break;
        }
    }
    if(!pd){
        cout << "资源数不足,陷入等待\n";
        return;
    }

    sum = 0;
    for(i=0; i<m; i++)
    {
        work[i] = available[i] - req[i];
        need[p][i] -= req[i];
        allow[p][i] += req[i];
    }
    memset(finish, false, sizeof(finish));

    isSafe();
    if(sum > 0){
        cout << "可以分配资源\n";
    }
    else{
        cout << "不可分配资源\n";
        for(i=0; i<m; i++)
        {
            need[p][i] += req[i];
            allow[p][i] -= req[i];
        }
    }
}

int main()
{
    //二进程请求资源 000 
    int req[] = {0, 0, 0};
    request(2, req);
    return 0;
 } 

4. 运行结果

银行家算法实现——找出所有安全序列

三. 找出所有安全序列

以上的程序找出了一种安全性序列,从银行家算法的角度上完成了任务,如何找出所有的安全性序列呢?

1. DFS

找出所有解,一般这种问题都是由深度优先搜索来完成。程序很简单,一般熟悉深搜框架的都能很快写完。

void dfs(int k)
{
    if(k == n){     //找到了一种安全性序列,打印输出 
        cout << sum++ << ":";
        int i;
        for(i=0; i<ans.size(); i++)
        {
            cout << "P" << ans[i] << ",";
        }
        cout << endl;
        return;
    }   

    int i;
    for(i=0; i<n; i++){
        if(!finish[i])
        {
            int j;
            bool pd = true;
            for(j=0; j<m; j++)
            {
                if(need[i][j] > work[j])
                {
                    pd = false;
                }
            }
            if(pd){        //第i个进程满足要求,暂时放入安全性队列 
                for(j=0; j<m; j++)
                {
                    work[j] += allow[i][j];
                }
                finish[i] = true;
                ans.push_back(i);
                dfs(k+1);     //搜索下一层 

                ans.pop_back();       //回溯,将第i个进程所做的改变恢复 
                for(j=0; j<m; j++)
                {
                    work[j] -= allow[i][j];
                }
                finish[i] = false;
            }
        }
    }
}

时间复杂度为O((n * m) ^ n),可以看出,为了找出所有序列,时间复杂度较第一种大大提升,变成了指数级。

2. BFS

其实广度优先搜索也可以解决这个问题,上面的深搜借用了递归的思想,而广搜则是队列的思想,因为没有了递归,所以我们需要把每个点定义为一个状态,存下当前需要记录的信息,易于后面的回溯。

struct state
{
    int work[5];              //当前每种资源可用量 
    bool finish[5];           //当前状态每个进程选择情况 
    int cur;                  //当前安全性序列已选中几个 
    vector<int> ans;          //记录直到当前状态的安全性队列 
    state()
    {
        memset(finish, false, sizeof(finish));
    }
};

接下来先将资源数满足的进程先入队,然后进行bfs,每次队列首节点出队,由此节点扩展之后的子节点

queue<state> q;
int sum = 0;
void bfs()
{
    int i, j, k;
    for(i=0; i<n; i++)
    {
        bool pd = true;
        for(j=0; j<m; j++)
        {
            if(need[i][j] > work[j]){
                pd = false;
                break;
            }
        }
        if(pd){
            state s;
            for(j=0; j<m; j++)
            {
                s.work[j] = work[j] + allow[i][j];
            }
            s.ans.push_back(i);
            s.finish[i] = true;
            s.cur = 1; 
            q.push(s);
        }
    }

    while(!q.empty())
    {
        state p = q.front();
        q.pop();
        if(p.cur == n){
            cout << sum++ << ":";
            int i;
            for(i=0; i<p.ans.size(); i++)
            {
                cout << "P" << p.ans[i] << ",";
            }
            cout << endl;
        }

        for(i=0; i<n; i++)
        {
            if(!p.finish[i])
            {
                bool pd = true;
                for(j=0; j<m; j++)
                {
                    if(p.work[j] < need[i][j])
                    {
                        pd = false;
                        break;
                    }
                }

                if(pd){
                    for(j=0; j<m; j++)
                    {
                        p.work[j] += allow[i][j];
                    }
                    p.cur++;
                    p.finish[i] = true;
                    p.ans.push_back(i);
                    q.push(p);

                    for(j=0; j<m; j++)
                    {
                        p.work[j] -= allow[i][j];
                    }
                    p.cur--;
                    p.finish[i] = false;
                    p.ans.pop_back();
                }

            }
        }
    }
}

时间复杂度同样为O((n * m) ^ n)

3. 运行结果

银行家算法实现——找出所有安全序列